<!DOCTYPE html>
<html>
<head>
<title>规范汇总(2023年07月03日).md</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">

<style>
/* https://github.com/microsoft/vscode/blob/master/extensions/markdown-language-features/media/markdown.css */
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

body {
	font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif);
	font-size: var(--vscode-markdown-font-size, 14px);
	padding: 0 26px;
	line-height: var(--vscode-markdown-line-height, 22px);
	word-wrap: break-word;
}

#code-csp-warning {
	position: fixed;
	top: 0;
	right: 0;
	color: white;
	margin: 16px;
	text-align: center;
	font-size: 12px;
	font-family: sans-serif;
	background-color:#444444;
	cursor: pointer;
	padding: 6px;
	box-shadow: 1px 1px 1px rgba(0,0,0,.25);
}

#code-csp-warning:hover {
	text-decoration: none;
	background-color:#007acc;
	box-shadow: 2px 2px 2px rgba(0,0,0,.25);
}

body.scrollBeyondLastLine {
	margin-bottom: calc(100vh - 22px);
}

body.showEditorSelection .code-line {
	position: relative;
}

body.showEditorSelection .code-active-line:before,
body.showEditorSelection .code-line:hover:before {
	content: "";
	display: block;
	position: absolute;
	top: 0;
	left: -12px;
	height: 100%;
}

body.showEditorSelection li.code-active-line:before,
body.showEditorSelection li.code-line:hover:before {
	left: -30px;
}

.vscode-light.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(0, 0, 0, 0.15);
}

.vscode-light.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(0, 0, 0, 0.40);
}

.vscode-light.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

.vscode-dark.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(255, 255, 255, 0.4);
}

.vscode-dark.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(255, 255, 255, 0.60);
}

.vscode-dark.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

.vscode-high-contrast.showEditorSelection .code-active-line:before {
	border-left: 3px solid rgba(255, 160, 0, 0.7);
}

.vscode-high-contrast.showEditorSelection .code-line:hover:before {
	border-left: 3px solid rgba(255, 160, 0, 1);
}

.vscode-high-contrast.showEditorSelection .code-line .code-line:hover:before {
	border-left: none;
}

img {
	max-width: 100%;
	max-height: 100%;
}

a {
	text-decoration: none;
}

a:hover {
	text-decoration: underline;
}

a:focus,
input:focus,
select:focus,
textarea:focus {
	outline: 1px solid -webkit-focus-ring-color;
	outline-offset: -1px;
}

hr {
	border: 0;
	height: 2px;
	border-bottom: 2px solid;
}

h1 {
	padding-bottom: 0.3em;
	line-height: 1.2;
	border-bottom-width: 1px;
	border-bottom-style: solid;
}

h1, h2, h3 {
	font-weight: normal;
}

table {
	border-collapse: collapse;
}

table > thead > tr > th {
	text-align: left;
	border-bottom: 1px solid;
}

table > thead > tr > th,
table > thead > tr > td,
table > tbody > tr > th,
table > tbody > tr > td {
	padding: 5px 10px;
}

table > tbody > tr + tr > td {
	border-top: 1px solid;
}

blockquote {
	margin: 0 7px 0 5px;
	padding: 0 16px 0 10px;
	border-left-width: 5px;
	border-left-style: solid;
}

code {
	font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
	font-size: 1em;
	line-height: 1.357em;
}

body.wordWrap pre {
	white-space: pre-wrap;
}

pre:not(.hljs),
pre.hljs code > div {
	padding: 16px;
	border-radius: 3px;
	overflow: auto;
}

pre code {
	color: var(--vscode-editor-foreground);
	tab-size: 4;
}

/** Theming */

.vscode-light pre {
	background-color: rgba(220, 220, 220, 0.4);
}

.vscode-dark pre {
	background-color: rgba(10, 10, 10, 0.4);
}

.vscode-high-contrast pre {
	background-color: rgb(0, 0, 0);
}

.vscode-high-contrast h1 {
	border-color: rgb(0, 0, 0);
}

.vscode-light table > thead > tr > th {
	border-color: rgba(0, 0, 0, 0.69);
}

.vscode-dark table > thead > tr > th {
	border-color: rgba(255, 255, 255, 0.69);
}

.vscode-light h1,
.vscode-light hr,
.vscode-light table > tbody > tr + tr > td {
	border-color: rgba(0, 0, 0, 0.18);
}

.vscode-dark h1,
.vscode-dark hr,
.vscode-dark table > tbody > tr + tr > td {
	border-color: rgba(255, 255, 255, 0.18);
}

</style>

<style>
/* Tomorrow Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */

/* Tomorrow Comment */
.hljs-comment,
.hljs-quote {
	color: #8e908c;
}

/* Tomorrow Red */
.hljs-variable,
.hljs-template-variable,
.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-deletion {
	color: #c82829;
}

/* Tomorrow Orange */
.hljs-number,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-meta,
.hljs-link {
	color: #f5871f;
}

/* Tomorrow Yellow */
.hljs-attribute {
	color: #eab700;
}

/* Tomorrow Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
	color: #718c00;
}

/* Tomorrow Blue */
.hljs-title,
.hljs-section {
	color: #4271ae;
}

/* Tomorrow Purple */
.hljs-keyword,
.hljs-selector-tag {
	color: #8959a8;
}

.hljs {
	display: block;
	overflow-x: auto;
	color: #4d4d4c;
	padding: 0.5em;
}

.hljs-emphasis {
	font-style: italic;
}

.hljs-strong {
	font-weight: bold;
}
</style>

<style>
/*
 * Markdown PDF CSS
 */

 body {
	font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "Meiryo";
	padding: 0 12px;
}

pre {
	background-color: #f8f8f8;
	border: 1px solid #cccccc;
	border-radius: 3px;
	overflow-x: auto;
	white-space: pre-wrap;
	overflow-wrap: break-word;
}

pre:not(.hljs) {
	padding: 23px;
	line-height: 19px;
}

blockquote {
	background: rgba(127, 127, 127, 0.1);
	border-color: rgba(0, 122, 204, 0.5);
}

.emoji {
	height: 1.4em;
}

code {
	font-size: 14px;
	line-height: 19px;
}

/* for inline code */
:not(pre):not(.hljs) > code {
	color: #C9AE75; /* Change the old color so it seems less like an error */
	font-size: inherit;
}

/* Page Break : use <div class="page"/> to insert page break
-------------------------------------------------------- */
.page {
	page-break-after: always;
}

</style>

<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
</head>
<body>
  <script>
    mermaid.initialize({
      startOnLoad: true,
      theme: document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast')
          ? 'dark'
          : 'default'
    });
  </script>
<p><img src="http://192.168.1.29:29090/fpi/wjfw/preview?wjbh=f10c86717f4b498290a8814608b718bd" alt="封面"></p>
<blockquote>
<p>文档版本信息</p>
</blockquote>
<table>
<thead>
<tr>
<th>版本</th>
<th>时间</th>
<th>说明</th>
<th>人员</th>
</tr>
</thead>
<tbody>
<tr>
<td>v1.0.0</td>
<td>2023年2月15日</td>
<td>初版定版</td>
<td>郑佳勇、赖湘林、张述江、张鹏、刘显久、张阳、庄代云</td>
</tr>
<tr>
<td>v1.0.1</td>
<td>2023年3月09日</td>
<td>新增部分规范</td>
<td>郑佳勇、赖湘林、张述江、张鹏、刘显久、张阳、庄代云</td>
</tr>
<tr>
<td>v1.0.2</td>
<td>2023年5月21日</td>
<td>新增部分规范，调整不合理的规范</td>
<td>郑佳勇、赖湘林、张述江、张鹏、刘显久、张阳、庄代云</td>
</tr>
</tbody>
</table>
<h1 id="%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83">编码规范</h1>
<h2 id="%E5%91%BD%E5%90%8D%E8%A7%84%E8%8C%83">命名规范</h2>
<p>全局约束：所有命名应遵循汉字拼音首字母拼写规则</p>
<ol>
<li>package命名</li>
</ol>
<ul>
<li>包名使用全小写，要求命名能体现具体含义，常规检察院项目以<code>com.tfswx.jcyw.xxxx</code>
作为项目全局package，公司公用工具类组件等可采用<code>com.tfswx.xxxx</code>作为项目全局package；</li>
</ul>
<ol start="2">
<li>类、接口命名</li>
</ol>
<ul>
<li>类、接口命名使用UpperCamelCase（驼峰，首字母大写）风格；</li>
<li>命名要求表达清楚类、接口含义，格式如下：
<ul>
<li>实体类、dao接口类
<ul>
<li>实体类
以数据库表名去掉<code>T_</code>后，使用首字母大写的驼峰命名格式，如：<code>ZzjgXtRybm</code></li>
<li>dao接口、dao接口对应的xml文件
以实体类全名加上ORM框架通用后缀，目前使用Mybatis，则通用后缀为Mapper，首字母大写的驼峰命名格式，如：<code>DmXtFldmMapper.java</code>、<code>DmXtFldmMapper.xml</code>
等`；</li>
</ul>
</li>
<li>业务层接口、业务实现类、控制层类
以该类需要实现的功能拼音首字母简写，并以对应package作为后缀格式命名，如：<code>SpglController</code>，<code>XtpzService</code>，<code>RzglServiceImpl</code></li>
<li>数据传输对象
绝大部分Controller出入参与Service相同，因此遵循相同规范，以名词+动词组合，与实现方法呼应，名词为实现功能，动词为方法动作，列表查询入參以<code>QueryParam</code>
结尾、其它入參以<code>InputDTO</code>结尾；列表查询出參以<code>QueryResult</code>结尾，其它出參以<code>OutputDTO</code>
结尾；如：<code>GndyAddInputDTO</code>，<code>FldmGetOuputDTO</code>，<code>RyxxQueryParam</code>，<code>RyxxQueryResult</code>等；</li>
<li></li>
</ul>
工具类、枚举类、任务类、配置类等其他以该类需要实现的功能拼音首字母简写，并以对应package作为后缀格式命名，如：<code>MybatisConfig</code>，<code>TbxxTask</code>，<code>JsonUtil</code>
等；
<ul>
<li>
<p>内部类  <strong>【20230309新增】</strong>
DTO的内部类禁止多层嵌套，内部类命名应与变量对应，采用首字母大写，拼音首字母格式，如：</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 【正例】</span>

<span class="hljs-comment">/**
 * 新增组织机构入参
 *
 * <span class="hljs-doctag">@author</span> 庄代云
 * <span class="hljs-doctag">@date</span> 2023-03-10
 */</span>
<span class="hljs-meta">@Data</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ZzjgAddInputDTO</span> </span>{

    <span class="hljs-keyword">private</span> String dwbm;
    <span class="hljs-keyword">private</span> List&lt;Bmxx&gt; bmxxList;

    <span class="hljs-meta">@Data</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Bmxx</span> </span>{
        
        <span class="hljs-keyword">private</span> String bmbm;
        <span class="hljs-keyword">private</span> List&lt;Ryxx&gt; ryxxList;

    }
    
    <span class="hljs-meta">@Data</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Ryxx</span> </span>{
        
        <span class="hljs-keyword">private</span> String rybm;
    }
}

<span class="hljs-comment">// 【反例】</span>

<span class="hljs-comment">/**
 * 新增组织机构入参
 *
 * <span class="hljs-doctag">@author</span> 庄代云
 * <span class="hljs-doctag">@date</span> 2023-03-10
 */</span>
<span class="hljs-meta">@Data</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ZzjgAddInputDTO</span> </span>{

    <span class="hljs-keyword">private</span> String dwbm;
    <span class="hljs-keyword">private</span> List&lt;Bmxx&gt; bmxxList;

    <span class="hljs-meta">@Data</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Bmxx</span> </span>{
        
        <span class="hljs-keyword">private</span> String bmbm;
        <span class="hljs-keyword">private</span> List&lt;Ryxx&gt; ryxxList;

        <span class="hljs-meta">@Data</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Ryxx</span> </span>{

            <span class="hljs-keyword">private</span> String rybm;
        }
    }
    
}

</div></code></pre>
</li>
</ul>
</li>
</ul>
<ol start="3">
<li>
<p>方法命名</p>
<ul>
<li>
<p>方法命名采用lowerCamelCase（驼峰，首字母小写）风格，要求表达清楚方法所实现的功能，以动词 +
名词的方式，可以用By，如：<code>queryFldm</code>，<code>updateRyxx</code>，<code>queryDwryByRybm</code>等；</p>
</li>
<li>
<p>常见方法命名动词详见下表： <strong>【20230521修改】</strong></p>
<p>| 动作    | 命名        | 说明                | 示例            |
|-------|-----------|-------------------|---------------|
| 添加    | add       | 新增数据              | addFldm       |
| 保存    | save      | 保存数据（一次性增删改）      | saveCsyls     |
| 修改    | update    | 修改数据              | updateRyxx    |
| 同步    | sync      | 同步数据              | syncAjjbxx    |
| 刷新    | refresh   | 刷新数据              | refreshAjjbxx |
| 清空    | clear     | 清空数据              | clearLogs     |
| 取消删除  | undelete  | 取消删除              | undeleteLogs  |
| 逻辑删除  | delete    | 逻辑删除数据            | deleteRyxx    |
| 物理删除  | remove    | 物理删除数据            | removeRyxx    |
| 复制    | copy      | 复制数据              | copyFile      |
| 查询    | query     | 列表查询              | queryZclb     |
| 查询    | get       | 单条数据查询            | getDwbm       |
| 上传    | upload    | 上传文件              | uploadTb      |
| 下载    | download  | 下载文件              | downloadFj    |
| 导入    | import    | 导入数据              | importAjxx    |
| 导出    | export    | 导出数据（word、excel等） | exportZclb    |
| 登录    | login     | 系统登录              | login         |
| 登出    | logout    | 退出登录              | logout        |
| 识别    | recognize | 识别数据              | recognizeAjxx |
| 发布    | release   | 发布数据              | releaseGg     |
| 撤销/撤回 | recall    | 撤销/撤回数据           | recallTzgg    |
| 校验/验证 | check     | 校验/验证/检查数据        | checkZw       |
| 执行    | execute   | 执行                | executeXxx    |
| 发送    | send      | 发送                | sendXx        |
| 完成    | finish    | 完成                | finishSp      |
| 接收    | receive   | 接收数据              | receiveWsrjxx |
| 提取    | extract   | 提取数据              | extractWsnr   |
| 计算    | calculate | 计算数据              | calculateXscj |
| 关闭    | close     | 关闭                | close         |
| 开启    | open      | 开启                | open          |
| 构建    | build     | 构建                | build         |
| 创建、生成 | create    | 创建审批              | createSp      |</p>
</li>
</ul>
</li>
<li>
<p>变量、对象属性命名</p>
<ul>
<li>
<p>实体、数据传输对象中的属性名与数据库表字段名保持一致，全小写；</p>
</li>
<li>
<p>所有集合类型（List，Map、Set等）类型变量必须添加对应后缀，如：<code>xxxList</code>，<code>xxxSet</code>，<code>xxxMap</code>等；</p>
</li>
<li>
<p>常规变量命名采用拼音首字母，全小写风格定义，要求命名能清楚表达变量表达含义，禁止无意义的变量命名；</p>
<pre class="hljs"><code><div> <span class="hljs-comment">// 【正例】</span>
 Integer sl = <span class="hljs-number">0</span>;
 String dwbm = <span class="hljs-string">"980000"</span>;

 <span class="hljs-comment">// 【反例】</span>
 String s = <span class="hljs-string">"xxx"</span>;
 String param1 = <span class="hljs-string">"xxx"</span>;
 <span class="hljs-keyword">int</span> a = <span class="hljs-number">1</span>;
 <span class="hljs-keyword">double</span> b1 = <span class="hljs-number">0</span>d;
</div></code></pre>
</li>
<li>
<p>对象变量命名使用对象全称，首字母小写形式，如：<code>zzjgXtDwbmMapper</code>，<code>sqlSession</code>，<code>fldmService</code>等；</p>
</li>
<li>
<p>同一个方法中，定义的变量需要类型转换，或定义的变量存在歧义时，那么应在名称后添加后缀，具体情况如下表；</p>
<p>| 类型              | 后缀   | 命名     | 示例                                                         |
| ----------------- | ------ | -------- | ------------------------------------------------------------ |
| java.util.Date    | Date   | xxxDate  | Date kssjDate = DateUtil.now();                              |
| java.lang.String  | Str    | xxxStr   | String kssjStr = DateUtil.format(kssjDate);                  |
| java.lang,Integer | Int    | xxxInt   | Integer slInt = 0;<br />int slInt = 0;                       |
| 数组              | Arr    | xxxArr   | String[] rybmArr = rybm.split(&quot;,&quot;);<br />Byte[] dwbmArr = new Byte[1024]; |
| java.lang.Double  | Double | xxDouble | Double jeDouble = Double.parseDouble();                      |
| java.lang.Long    | Long   | xxxLong  | Long timeLong = Long.valueOf();                              |</p>
</li>
</ul>
</li>
<li>
<p>常量命名
常量命名全部大写，词语间用下划线隔开，力求语义表达完整清楚，不要嫌名字长；</p>
</li>
<li>
<p>数据库表、字段、索引命名</p>
<ul>
<li>Mysql表名、字段名小写，其它如Oracle、达梦，表名、字段名全大写；</li>
<li>表名以<code>T_</code>开头，词语之间使用下划线分隔，格式为：T_功能模块_表类型_表名；如：<code>T_ZZJG_XT_DWBM</code>，<code>T_SP_YX_SPRW</code>等；</li>
<li>主键命名，系统表以BM结尾，运行表以BH结尾，分类代码以DM结尾；其它字段命名以字段中文拼音首字母缩写命名；</li>
<li>字段中禁止使用下划线；</li>
<li>每张表必须包含创建时间（CJSJ），最后修改时间（ZHXGSJ），是否删除（SFSC）三个系统字段；</li>
<li>索引以IDX+表全名+字段名方式，下划线连接，格式为：<code>IDX_表全名_字段1_字段2</code></li>
</ul>
</li>
<li>
<p>请求地址命名</p>
<ul>
<li>Controller类注解统一使用@RequestMapping注解，与Controller前缀一致，采用全小写格式；</li>
<li>Controller类注解value属性必须添加<code>api</code>前缀；</li>
<li>方法中使用@GetMapping，@PostMapping等，具体情况根据前后端约定的请求方式使用，value命名采用首字母小写的驼峰形式，建议与方法名相同；</li>
<li>禁止使用PathVariables方式<pre class="hljs"><code><div><span class="hljs-comment">// 【正例】</span>
<span class="hljs-meta">@RequestMapping</span>(<span class="hljs-string">"api/gjgl"</span>)
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GjglController</span> </span>{
    <span class="hljs-comment">// 【正例】</span>
    <span class="hljs-meta">@PostMapping</span>(<span class="hljs-string">"queryGjxx"</span>)
    <span class="hljs-function"><span class="hljs-keyword">public</span> PageResult&lt;GjxxQueryOutputDTO&gt; <span class="hljs-title">queryGjxx</span><span class="hljs-params">()</span> </span>{}
}

<span class="hljs-comment">// 【反例】</span>
<span class="hljs-meta">@RequestMapping</span>(<span class="hljs-string">"gjgl"</span>)
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GjglController</span> </span>{
    <span class="hljs-comment">// 【反例】</span>
    <span class="hljs-meta">@PostMapping</span>(<span class="hljs-string">"getGjxx/{gjbh}"</span>)
    <span class="hljs-function"><span class="hljs-keyword">public</span> GjxxGetOutputDTO <span class="hljs-title">getGjxx</span><span class="hljs-params">(@PathVariable String gjbh)</span> </span>{}
}
</div></code></pre>
</li>
</ul>
</li>
</ol>
<h2 id="java%E4%BB%A3%E7%A0%81%E6%A0%BC%E5%BC%8F">java代码格式</h2>
<ol>
<li>
<p>如果是大括号内为空，则简洁的写成<code>{}</code>即可，大括号中间无需换行和空格；如果是非空代码块则：</p>
<ul>
<li>左大括号前不换行；</li>
<li>左大括号后换行；</li>
<li>右大括号前换行；</li>
<li>右大括号后还有else等代码则不换行；</li>
<li>表示终止的右大括号后必须换行；</li>
</ul>
</li>
<li>
<p>左小括号和右边相邻字符之间不出现空格；右小括号和左边相邻字符之间也不出现空格；而左大括号前需要加空格。</p>
</li>
<li>
<p><code>if</code>、<code>for</code>、<code>while</code>、<code>switch</code>、<code>do</code>等保留字与括号之间都必须加空格。</p>
</li>
<li>
<p>任何二目、三目运算符的左右两边都必须加空格。
包括赋值运算符=，逻辑运算符&amp;&amp;、||等，加减乘除符号等</p>
</li>
<li>
<p>代码采用4个空格进行缩进，禁止使用tab字符。
如果使用tab缩进，必须设置1个tab为4个空格。IDEA设置tab为4个空格时，请勿勾选Use tab character。</p>
<pre class="hljs"><code><div> <span class="hljs-comment">// ***1 ~ 5点示例***</span>
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
 	<span class="hljs-comment">// 缩进4个空格</span>
 	String say = <span class="hljs-string">"hello"</span>;
 	<span class="hljs-comment">// 运算符的左右必须有一个空格</span>
 	<span class="hljs-keyword">int</span> flag = <span class="hljs-number">0</span>;
 	<span class="hljs-comment">// 关键字if与括号之间必须有一个空格，括号内的f与左括号，0与右括号不需要空格</span>
 	<span class="hljs-keyword">if</span> (flag == <span class="hljs-number">0</span>) {
 		System.out.println(say);
 	}

 	<span class="hljs-comment">// 左大括号前加空格且不换行；左大括号后换行</span>
 	<span class="hljs-keyword">if</span> (flag == <span class="hljs-number">1</span>) {
 		System.out.println(world);
 		<span class="hljs-comment">// 右大括号前换行，右边大括号后有else，不换行</span>
 	} <span class="hljs-keyword">else</span> {
 		System.out.println(<span class="hljs-string">"OK"</span>);
 		<span class="hljs-comment">// 在右大括号后直接结束，则必须换行</span>
 	}
 }
</div></code></pre>
</li>
<li>
<p>注释的双斜杠与注释内容之间有且仅有一个空格</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 示例注释，请注意在双斜杠之后有且仅有一个空格</span>
String commentString = <span class="hljs-keyword">new</span> String();
</div></code></pre>
</li>
<li>
<p>在进行类型强制转换时，右括号与强制转换值之间不需要任何空格隔开</p>
<pre class="hljs"><code><div><span class="hljs-keyword">long</span> first = <span class="hljs-number">1000000000000L</span>;
<span class="hljs-keyword">int</span> second = (<span class="hljs-keyword">int</span>)first + <span class="hljs-number">2</span>;
</div></code></pre>
</li>
<li>
<p>单行字符数限制不超过120个，若超出则需要进行换行；当存在多个方法连续调用时，每个方法调用必选换行，换行时必须遵循如下规则：</p>
<ul>
<li>第二行相对于第一行缩进4个空格，从第三行开始，不在继续缩进；</li>
<li>运算符与下文一起换行；</li>
<li>方法调用的点符号与下文一起换行；</li>
<li>方法调用中的多个参数需要换行时，在逗号之后进行；</li>
<li>在括号前禁止换行；</li>
</ul>
</li>
<li>
<p>方法参数在定义和传入时，多个参数逗号后面必须加空格</p>
<pre class="hljs"><code><div> method(arg1, arg2, arg3);
</div></code></pre>
</li>
<li>
<p>不需要添加额外的空格来使变量的赋值等号与上一行对应位置的等号对齐</p>
</li>
<li>
<p>不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性<code>任何情况下，没有必要插入多个空行来进行分隔</code></p>
</li>
<li>
<p>项目中禁止使用MybatisPlus <strong>【20230309新增】</strong></p>
</li>
<li>
<p>HttpServletRequest，HttpServletResponse对象禁止传入Service层  <strong>【20230309新增】</strong></p>
</li>
<li>
<p>UserInfo(当前登录用户信息)应该在Controller层进行获取，然后作为参数传递到Service，禁止直接在Service层直接获取UserInfo
<strong>【20230309新增】</strong></p>
</li>
<li>
<p>所有对象中定义的属性，注入的类必须使用<code>private</code>修饰； <strong>【20230309新增】</strong></p>
<pre class="hljs"><code><div><span class="hljs-comment">// 【正例】</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ZzjgXtRybm</span> </span>{
    <span class="hljs-keyword">private</span> String dm;
    <span class="hljs-keyword">private</span> String mc;
}

<span class="hljs-comment">// 【反例】</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{
    ZzjgXtRybmMapper zzjgXtRybmMapper;
}
</div></code></pre>
</li>
<li>
<p>当mapper层接口入参大于1个时，必须使用对象将入参封装；当参数只有一个时，不需要使用对象包裹，但必须添加<code>@Param</code>注解； *
<em>【20230310新增】</em>*</p>
<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">DmXtFldmMapper</span> </span>{
    <span class="hljs-comment">// 【正例】</span>
	<span class="hljs-function">DmXtFldm <span class="hljs-title">getFldm</span><span class="hljs-params">(@Param(<span class="hljs-string">"dm"</span>)</span> String dm)</span>;
    <span class="hljs-comment">// 【正例】</span>
    <span class="hljs-function">List&lt;FldmQueryResult&gt; <span class="hljs-title">queryFldm</span><span class="hljs-params">(FldmQueryParam param)</span></span>;
    
    <span class="hljs-comment">// 【反例】</span>
    <span class="hljs-function">DmXtFldm <span class="hljs-title">getFldm</span><span class="hljs-params">(String dm)</span></span>;
    <span class="hljs-comment">// 【反例】</span>
    <span class="hljs-function">List&lt;FldmQueryResult&gt; <span class="hljs-title">queryFldm</span><span class="hljs-params">(@Param(<span class="hljs-string">"lbbm"</span>)</span> String lbbm, @<span class="hljs-title">Param</span><span class="hljs-params">(<span class="hljs-string">"mc"</span>)</span> String mc)</span>;
}
</div></code></pre>
</li>
<li>
<p>禁止将<code>InputDTO</code>，<code>OutputDTO</code>传入Mapper层，Mapper层中对应的get方法的入參出參对象可替换为实体或<code>QueryParam</code>
及<code>QueryResult</code>；<strong>【20230521新增】</strong></p>
</li>
<li>
<p>能使用现成工具类，就不要自己写，减少bug率，常用工具类详见《工具类》章节；<strong>【20230521新增】</strong></p>
</li>
</ol>
<h2 id="sql%E4%BB%A3%E7%A0%81%E6%A0%BC%E5%BC%8F">SQL代码格式</h2>
<ol>
<li>
<p>关键词中线对齐</p>
</li>
<li>
<p>所有查询必须有明确的排序字段</p>
</li>
<li>
<p>SQL语句中关键字必须大写，表名与字段名必须小写</p>
</li>
<li>
<p>不要使用count(列名)或count(常量)来替代count(*),count(*)是SQL92定义的标准统计行数的语法，跟数据库无关，跟NULL和非NULL无关
<code>count(*)会统计值为NULL行，而count(列名)不会统计此列为NULL值的行</code></p>
</li>
<li>
<p>当某一列的值全是NULL时，count(col)的返回结果为0，但sum(col)的返回结果为 NULL，因此使用 sum()时需注意 NPE 问题</p>
</li>
<li>
<p>禁止使用特殊函数，常见通用函数如下：</p>
<p>| <strong>函数</strong>  | <strong>说明</strong>                              | <strong>示例代码（达梦）</strong>          | <strong>示例代码（mysql）</strong>         |
| --------- | ------------------------------------- | ----------------------------- | ----------------------------- |
| <code>COUNT</code>   | 统计查询结果的行数                    | <code>SELECT COUNT(*) FROM a;</code>     | <code>SELECT COUNT(*) FROM a;</code>     |
| <code>MIN</code>     | 查询指定列的最小值                    | <code>SELECT MIN(a) FROM b;</code>       | <code>SELECT MIN(a) FROM b;</code>       |
| <code>MAX</code>     | 查询指定列的最大值                    | <code>SELECT MAX(a) FROM b;</code>       | <code>SELECT MAX(a) FROM b;</code>       |
| <code>SYSDATE</code> | 当前时间，格式为：yyyy-MM-dd HH:mm:ss | <code>SELECT SYSDATE() FROM dual;</code> | <code>SELECT SYSDATE() FROM dual;</code> |
| <code>SUM</code>     | 返回指定列值的总和                    | <code>SELECT SUM(a) FROM b;</code>       | <code>SELECT SUM(a) FROM b;</code>       |</p>
</li>
<li>
<p>使用 IS NULL 来判断是否为NULL值
<code>NULL 与任何值的直接比较都为 NULL</code></p>
<ul>
<li>NULL &lt;&gt; NULL 的返回结果是 NULL，而不是 false；</li>
<li>NULL = NULL 的返回结果是 NULL，而不是 true；</li>
<li>NULL &lt;&gt; 1 的返回结果是 NULL，而不是 true；</li>
</ul>
</li>
<li>
<p>代码中写分页查询逻辑时，若count为0应直接返回，避免执行后面的分页语句</p>
</li>
<li>
<p>禁止使用存储过程，存储过程难以调试和扩展，更没有移植性</p>
</li>
<li>
<p><s>数据订正(特别是删除或修改记录操作)时，要先select，避免出现误删除，确认无误才能执行更新语句</s> <strong>【20230608修改】</strong></p>
</li>
<li>
<p>对于数据库中表记录的查询和变更，只要涉及多个表，都需要在列名前加表的别名(或 表名)进行限定</p>
</li>
<li>
<p>在表查询中，一律禁止使用 * 作为查询的字段列表，需要哪些字段必须明确写明：</p>
<ul>
<li>增加查询分析器解析成本；</li>
<li>增减字段容易与 resultMap 配置不一致；</li>
<li>无用字段增加网络消耗；</li>
</ul>
</li>
<li>
<p>sql.xml 配置参数使用:#{}，#param# 不要使用${} 此种方式容易出现 SQL 注入</p>
</li>
<li>
<p>禁止直接拿HashMap与Hashtable作为查询结果集的输出</p>
</li>
<li>
<p><code>update\insert\delete</code>操作禁止使用<code>&lt;if&gt;</code>标签进行条件式修改</p>
</li>
<li>
<p><code>insert</code>操作禁止使用<code>&lt;foreach&gt;</code>标签进行批量写入 <strong>【20230521新增】</strong></p>
</li>
<li>
<p>不是必须使用的标签的场景（已明确where条件，不存在动态条件），禁止使用标签 <strong>【20230521新增】</strong></p>
</li>
<li>
<p>编写sql时，不要为了方便，直接将所有字段都查询出来，需要哪些查哪些，添加、修改同理 <strong>【20230521新增】</strong></p>
</li>
<li>
<p>所有修改、删除、查询方法都需要加上<code>sfsc = 'N'</code>的条件 <strong>【20230521新增】</strong></p>
</li>
<li>
<p>数据订正(特别是删除或修改记录操作)时，执行完update之后需要对影响行数进行判断，以此判断业务上的是否修改（或删除）成功（BATCH执行器
<strong>暂时</strong> 无需进行判断） <strong>【20230608新增】</strong></p>
</li>
</ol>
<h2 id="%E6%B3%A8%E9%87%8A%E8%A7%84%E8%8C%83">注释规范</h2>
<ol>
<li>每个类必须包含类注释，注释必须包含类中文说明，作者（真实的中文名），创建时间：  <strong>【20230321修改】</strong><pre class="hljs"><code><div><span class="hljs-comment">/**
 * 变更信息Mapper
 *
 * <span class="hljs-doctag">@author</span> 张阳
 * <span class="hljs-doctag">@date</span> 2022/11/10
 */</span>
</div></code></pre>
</li>
<li>每个业务层、dao层方法必须包含方法注释，注释必须包含方法中文说明，入參说明，出參说明，抛出异常说明：<pre class="hljs"><code><div><span class="hljs-comment">/**
 * 添加收藏夹
 *
 * <span class="hljs-doctag">@param</span> inputDTO 添加收藏夹入參
 */</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">addScj</span><span class="hljs-params">(ScjAddInputDTO inputDTO)</span></span>;

<span class="hljs-comment">/**
 * 保存接口相关信息（接口基本信息，接口授权，变更信息）
 *
 * <span class="hljs-doctag">@param</span> inputDTO 前端传入的参数
 * <span class="hljs-doctag">@param</span> user 用户信息
 * <span class="hljs-doctag">@return</span> 保存接口变更后的返回结果
 */</span>
<span class="hljs-function"><span class="hljs-keyword">private</span> JkglBcOutputDTO <span class="hljs-title">bcjkgl</span><span class="hljs-params">(JkglBcInputDTO inputDTO, UserInfo user)</span></span>;
</div></code></pre>
</li>
<li>实体属性必须添加注释，枚举属性中没有中文描述时，也必须添加注释；<pre class="hljs"><code><div>    <span class="hljs-comment">/**
     * 工具编号
     */</span>
    <span class="hljs-keyword">private</span> String gjbh;

    <span class="hljs-comment">/**
     * 登录日志
     */</span>
    DL(<span class="hljs-string">"0301000000001"</span>),
    <span class="hljs-comment">/**
     * 操作日志
     */</span>
    CZ(<span class="hljs-string">"0301000000002"</span>),
    <span class="hljs-comment">/**
     * 异常日志
     */</span>
    YC(<span class="hljs-string">"0301000000003"</span>),
</div></code></pre>
</li>
<li>方法内部单行注释，禁止放在代码后，应在被注释代码上方单独起一行，使用 // 注释。方法内部多行注释使用 /* */ 注释，注意与代码对齐。<pre class="hljs"><code><div> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">test</span><span class="hljs-params">()</span> </span>{
     <span class="hljs-comment">// 1.删除数据</span>
     删除数据代码。。。
     <span class="hljs-comment">// 2.添加数据</span>
     添加数据代码。。。
 }

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">query</span><span class="hljs-params">()</span> </span>{
     <span class="hljs-comment">/*
      * 1.xxxx
      * 2.xxxx
      * 3.xxx
      */</span>
     代码块
 }
</div></code></pre>
</li>
<li>方法由于业务逻辑/需求变更等原因需要对原代码进行修改，修改后必须添加注释，注释说明修改时间，修改原因；</li>
<li>代码提交时如有未完成的代码，应添加// TODO标记，说明待办事项，预处理时间，后续处理完成后，及时删除该注释标记；</li>
<li>暂时不使用的代码块，使用单行或多行注释，确认不需要的代码必须及时删除，减少注释代码块；</li>
<li>修改方法名、入參出參修改，必须同步修改注释，保持代码与注释的一致性；</li>
<li>复杂的业务逻辑必须添加注释，描述清楚处理步骤；</li>
<li>注入配置属性必须加入注释说明 <strong>【20230703添加】</strong></li>
</ol>
<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{
    <span class="hljs-comment">/**
     * 统一业务地址
     */</span>
    <span class="hljs-meta">@Value</span>(<span class="hljs-string">"${tyywdz}"</span>)
    <span class="hljs-keyword">private</span> String tyydwz;
}
</div></code></pre>
<h2 id="%E6%B3%A8%E8%A7%A3">注解</h2>
<ol>
<li>声明bean的注解</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@Component</span>：泛指各种组件
<span class="hljs-meta">@RestController</span>、<span class="hljs-meta">@Service</span>、<span class="hljs-meta">@Mapper</span>都可以称为 <span class="hljs-meta">@Component</span>
<span class="hljs-meta">@RestController</span>：控制层
<span class="hljs-meta">@Service</span>：业务层
<span class="hljs-meta">@Mapper</span>：数据访问层
</div></code></pre>
<ol start="2">
<li>注入bean的注解</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@Resource</span>
</div></code></pre>
<ol start="3">
<li>Java配置类相关注解</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@Configuration</span>：声明当前类为配置类
<span class="hljs-meta">@Bean</span>：注解在方法上，声明当前方法的返回值为一个bean，替代xml中的方式
<span class="hljs-meta">@ComponentScan</span>：用于对Component进行扫描
</div></code></pre>
<ol start="4">
<li>切面（AOP）相关注解</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// Spring支持AspectJ的注解式切面编程</span>
<span class="hljs-meta">@Aspect</span>：声明一个切面
<span class="hljs-meta">@After</span>：在方法执行之后执行（方法上）
<span class="hljs-meta">@Before</span>：在方法执行之前执行（方法上）
<span class="hljs-meta">@Around</span>：在方法执行之前与之后执行（方法上）
<span class="hljs-meta">@PointCut</span>：声明切点
<span class="hljs-meta">@EnableAspectJAutoProxy</span>：开启Spring对AspectJ代理的支持，入口类上标注
</div></code></pre>
<ol start="5">
<li>@Bean的属性支持</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@Scope</span>设置类型包括：设置Spring容器如何新建Bean实例
        Singleton：单例,一个Spring容器中只有一个bean实例，默认模式
        Protetype：每次调用新建一个bean
        Request：web项目中，给每个http request新建一个bean
        Session：web项目中，给每个http session新建一个bean
        GlobalSession：给每一个 global http session 新建一个 Bean 实例
</div></code></pre>
<ol start="6">
<li><code>@Value</code>注解
注入普通字符、注入操作系统属性、注入表达式结果、注入其它bean属性、注入文件资源、注入网站资源、注入配置文件</li>
<li>环境切换</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@Profile</span>：指定组件在哪个环境的情况下才能被注册到容器中，不指定，任何环境下都能注册这个组件。
<span class="hljs-meta">@Conditional</span>：通过实现Condition接口，并重写matches方法，从而决定该bean是否被实例化。
</div></code></pre>
<ol start="8">
<li>异步相关</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@EnableAsync</span>：配置类中通过此注解开启对异步任务的支持
<span class="hljs-meta">@Async</span>：在实际执行的bean方法使用该注解来声明其是一个异步任务（方法上或类上所有的方法都将异步，需要<span class="hljs-meta">@EnableAsync</span>开启异步任务）
</div></code></pre>
<ol start="9">
<li>定时任务相关</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@EnableScheduling</span>：在配置类上使用，开启计划任务的支持
<span class="hljs-meta">@Scheduled</span>：来申明这是一个任务，包括cron,fixDelay,fixRate等类型（方法上，需先开启计划任务的支持）
</div></code></pre>
<ol start="10">
<li>Enable***注解说明</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@EnableAspectAutoProxy</span>：开启对AspectJ自动代理的支持
<span class="hljs-meta">@EnableAsync</span>：开启异步方法的支持
<span class="hljs-meta">@EnableScheduling</span>：开启计划任务的支持
<span class="hljs-meta">@EnableWebMvc</span>：开启web MVC的配置支持
<span class="hljs-meta">@EnableConfigurationProperties</span>：开启对<span class="hljs-meta">@ConfigurationProperties</span>注解配置Bean的支持
<span class="hljs-meta">@EnableJpaRepositories</span>：开启对 SpringData JPA Repository 的支持
<span class="hljs-meta">@EnableTransactionManagement</span>：开启注解式事务的支持
<span class="hljs-meta">@EnableCaching</span>：开启注解式的缓存支持
</div></code></pre>
<ol start="11">
<li>Swagger相关  <strong>【20230703修改】</strong></li>
</ol>
<pre class="hljs"><code><div><span class="hljs-meta">@Api</span>(tags = <span class="hljs-string">"Controller中文说明"</span>)：用于Contoller类上，说明该Controller类的中文，必须属性tags
<span class="hljs-meta">@ApiOperation</span>(<span class="hljs-string">"用户登录"</span>)：作用于Controller的方法，用中文说明每个方法，必须属性value
<span class="hljs-meta">@ApiModel</span>(<span class="hljs-string">"用户登录入参"</span>)：用于DTO，Query类上，说明该类的中文用法，必须参数value
<span class="hljs-meta">@ApiModelProperty</span>(<span class="hljs-string">"人员编码"</span>)：用于DTO，Query类的属性上，说明属性的中文解释
</div></code></pre>
<ol start="12">
<li>参数校验注解,NotNlank之类的校验注解需要写中文message <strong>【20230703添加】</strong></li>
</ol>
<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{

    <span class="hljs-comment">/**
     * 正例
     */</span>
    <span class="hljs-meta">@NotBlank</span>(message = <span class="hljs-string">"字段名称不能为空"</span>)
    <span class="hljs-keyword">private</span> String zdmc;

    <span class="hljs-comment">/**
     * 反例1
     */</span>
    <span class="hljs-meta">@NotBlank</span>
    <span class="hljs-keyword">private</span> String zdmc;

    <span class="hljs-comment">/**
     * 反例2
     */</span>
    <span class="hljs-meta">@NotBlank</span>(message = <span class="hljs-string">"zdmc-不能为空"</span>)
    <span class="hljs-keyword">private</span> String zdmc;
}
</div></code></pre>
<h2 id="%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84-20230521%E4%BF%AE%E6%94%B9">项目结构  <strong>【20230521修改】</strong></h2>
<table>
<thead>
<tr>
<th>包</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td>api</td>
<td>用于存放所有Controller</td>
</tr>
<tr>
<td>config</td>
<td>config包用于存放项目的配置工具类</td>
</tr>
<tr>
<td>dao</td>
<td>dao包用与存放数据访问层接口类，在业务模块较多时，应先创建功能模块的package，再在功能模块下创建Dao接口</td>
</tr>
<tr>
<td>dto</td>
<td>dto包用于存放增删改查入參、出參文件，在业务模块较多时，应先创建功能模块的package，再在功能模块下创建DTO</td>
</tr>
<tr>
<td>enums</td>
<td>enums包用于存放枚举类文件</td>
</tr>
<tr>
<td>model</td>
<td>model包用于存放实体类文件，在业务模块较多时，应先创建功能模块的package，再在功能模块下创建实体类</td>
</tr>
<tr>
<td>query</td>
<td>query包用于存放列表查询入參、出參文件，在业务模块较多时，应先创建功能模块的package，再在功能模块下创建文件</td>
</tr>
<tr>
<td>service</td>
<td>service包用于存放业务层接口文件</td>
</tr>
<tr>
<td>service.impl</td>
<td>impl包用于存放业务层接口实现类文件</td>
</tr>
<tr>
<td>service.data</td>
<td>data包用于存放业务层内部调用类（如UserInfo，其它内部Service中定义的数据传输对象）</td>
</tr>
<tr>
<td>task</td>
<td>task包用于存放任务类文件，如定时任务等</td>
</tr>
<tr>
<td>util</td>
<td>util包用于存放工具类文件</td>
</tr>
<tr>
<td>constant</td>
<td>constant包用于存放系统常量类文件</td>
</tr>
</tbody>
</table>
<h2 id="%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86">异常处理</h2>
<ul>
<li>禁止使用try-catch包裹整个方法</li>
<li>可以通过预检查方式规避的异常，禁止使用try-catch处理</li>
<li>捕获异常是为了处理它，不要捕获了却什么都不做，如果不想处理，应抛出给最外层调用者，最外层使用者，必须处理该异常，并将其转换为用户可以理解的内容</li>
<li>catch块中禁止使用e.printStackTrace(),要么抛出异常信息，要么使用日志记录信息信息</li>
<li>流对象（InputStream/OutputStream）、资源对象（Cursor/SqlSession）必须关闭，使用finally或try-with-resources</li>
<li>finally中禁止使用return</li>
</ul>
<h2 id="%E6%97%A5%E5%BF%97%E8%AE%B0%E5%BD%95">日志记录</h2>
<ul>
<li>日志内容需详细描述记录内容，表达清楚业务、异常信息，禁止模糊不清的日志记录</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 【反例】</span>
log.error(<span class="hljs-string">"保存出错"</span>);
</div></code></pre>
<ul>
<li>禁止直接使用日志系统中的API，应使用日志框架中的API</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 【正例】</span>
<span class="hljs-meta">@Slf</span>4j
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ZzjgServiceImpl</span> </span>{
}
</div></code></pre>
<ul>
<li>记录日志信息时，字符串与变量之间禁止使用加号拼接；</li>
</ul>
<pre class="hljs"><code><div><span class="hljs-comment">// 【正例】</span>
log.info(<span class="hljs-string">"调用统一业务查询案件接口：{}，接口入參：{}，调用结果：{}"</span>,queryAjxxUrl,ajxxQueryParam,ajxxQueryResult);
        log.error(<span class="hljs-string">"修改系统配置信息出错，错误信息：{}"</span>,e.getMessage(),e);

<span class="hljs-comment">// 【反例】</span>
<span class="hljs-comment">// 字符串拼接固定损耗性能</span>
        log.info(<span class="hljs-string">"保存部门受案号"</span>+bmsah+<span class="hljs-string">"的案件成功"</span>);
<span class="hljs-comment">// 异常信息打印不全，只能打印出异常类信息，且没有异常堆栈信息</span>
        log.error(<span class="hljs-string">"保存部门受案号失败：{}"</span>,e);
<span class="hljs-comment">// 无法打印异常堆栈</span>
        log.error(<span class="hljs-string">"保存部门受案号失败：{}"</span>,e.getMessage());
</div></code></pre>
<ul>
<li>调用外部接口、第三方接口必须在调用前、调用后记录日志</li>
<li>记录日志时禁止使用JSON工具将对象转换为String，仅记录出业务相关属性值或者调用其对象的 toString() 方法</li>
<li>禁止使用System.out或System.error输出或使用e.printStackTrace()打印异常堆栈</li>
</ul>
<h2 id="git%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86%E5%8F%8A%E4%BB%A3%E7%A0%81%E6%8F%90%E4%BA%A4">git分支管理及代码提交</h2>
<h3 id="%E5%88%86%E6%94%AF%E7%AE%A1%E7%90%86">分支管理</h3>
<ul>
<li><strong>master</strong>：主干分支，用于发布，禁止在master分支上直接修改代码；</li>
<li><strong>dev</strong>：开发分支，用于日常开发整合、发布测试；</li>
<li><strong>dev-版本号</strong>：每个版本的开发分支，完成后合并到dev分支；</li>
<li><strong>dev-版本号-功能名称</strong>：每个版本，每个功能对应的开发分支，该分支由开发人员自己创建，完成后合并到dev-版本号；</li>
<li><strong>hotfix-bug</strong>：基于master的bug修复分支，每个bug创建一个分支，完成修复后合并至master；</li>
</ul>
<h3 id="%E4%BB%A3%E7%A0%81%E6%8F%90%E4%BA%A4">代码提交</h3>
<ul>
<li>提交说明必须明确说明本次提交代码主要包含内容，修改bug后提交代码，如来自bug系统，必须附上该bug对应url地址。</li>
<li>代码提交要遵循多次少量的原则，先提交到本地，待完成后在统一push到远程。 <strong>【20230309新增】</strong><pre class="hljs"><code><div>【新增】<span class="hljs-number">1</span>.xxxx；<span class="hljs-number">2</span>.xxxx
【修改】<span class="hljs-number">1</span>.xxxx；<span class="hljs-number">2</span>.xxxx
【删除】<span class="hljs-number">1</span>.xxxx；<span class="hljs-number">2</span>.xxxx
【bug修复】<span class="hljs-number">1</span>.修改xxx的问题，http:<span class="hljs-comment">//192.168.1.19/xxxxx</span>
</div></code></pre>
</li>
</ul>
<h1 id="%E7%BC%96%E7%A0%81%E7%BB%8F%E9%AA%8C">编码经验</h1>
<h2 id="%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B">数据类型</h2>
<ol>
<li>
<p>关于基本数据类型与包装数据类型的使用标准如下：</p>
<ul>
<li>所有的 POJO 类属性必须使用包装数据类型。</li>
<li>RPC 方法的返回值和参数必须使用包装数据类型。</li>
</ul>
<blockquote>
<p>说明：POJO 类属性没有初值是提醒使用者在需要使用时，必须自己显式地进行赋值，任何 NPE 问题，或者入库检查，都由使用者来保证。</p>
<p>正例：数据库的查询结果可能是 null，因为自动拆箱，用基本数据类型接收有 NPE 风险。</p>
<p>反例：某业务的交易报表上显示成交总额涨跌情况，即正负 x%，x 为基本数据类型，调用的 RPC 服务，调用不成功时，返回的是默认值，页面显示为
0%，这是不合理的，应该显示成中划线-。所以包装数据类型的 null 值，能够表示额外的信息，如：远程调用失败，异常退出。</p>
</blockquote>
</li>
</ol>
<blockquote></blockquote>
<ol start="2">
<li>
<p>定义数据对象 DO 类时，属性类型要与数据库字段类型相匹配。类型匹配表如下：</p>
<p>| <strong>属性类型</strong> | <strong>数据库字段类型（达梦）</strong> | <strong>数据库字段类型（Mysql）</strong> | <strong>备注</strong>                                   |
| ------------ | -------------------------- | --------------------------- | ------------------------------------------ |
| BigDecimal   | NUMBER                     | DECIMAL                     | 常用于金额等精度要求高的字段               |
| Boolean      |                            | BIT                         |                                            |
| Date         | DATETIME                   | DATETIME                    |                                            |
| Double       | NUMBER                     | DOUBLE                      |                                            |
| Float        | NUMBER                     | FLOAT                       |                                            |
| Integer      | NUMBER                     | INT                         | 达梦的NUMBER类型需限制精度及标度           |
| Long         | BIGINT                     | BIGINT                      |                                            |
| String       | VARCHAR/CHAR/CLOB          | VARCHAR/CHAR/TEXT           | 大字段类型根据各数据库使用场景需要特殊处理 |</p>
<blockquote>
<p>正例：数据库字段的 <code>bigint</code> 必须与类属性的 <code>Long</code> 类型相对应。</p>
<p>反例：某业务的数据库表 id 字段定义类型为 <code>bigint unsigned</code>，实际类对象属性为 <code>Integer</code>，随着 id
越来越大，超过 <code>Integer</code> 的表示范围而溢出成为负数，此时数据库 id 不支持存入负数抛出异常产生线上故障。</p>
</blockquote>
</li>
<li>
<p>类型与中括号紧挨相连来定义数组。</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 正例：</span>
<span class="hljs-keyword">int</span>[] arrayDemo;

<span class="hljs-comment">// 反例：</span>
<span class="hljs-keyword">int</span> arrayDemo[];
</div></code></pre>
</li>
</ol>
<h2 id="%E9%9B%86%E5%90%88">集合</h2>
<blockquote>
<p>Java中的集合主要分为四类，包括：<code>List</code> 列表、<code>Queue</code> 队列、<code>Set</code> 集合、<code>Map</code> 集合。集合使用场景建议：</p>
</blockquote>
<table>
<thead>
<tr>
<th><strong>集合</strong></th>
<th><strong>有序性</strong></th>
<th><strong>数据是否可重复</strong></th>
<th><strong>常用实现类</strong></th>
<th><strong>建议使用场景</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>List</code></td>
<td>有序</td>
<td>可重复</td>
<td><code>ArrayList、LinkedList、Vector、CopyOnWriteArryList、Stack等</code></td>
<td>当前对集合插入数据的效率要求不高，对集合数据加载要求较高的情况下，或者说是对集合中的数据读多写少的场景下，使用<code>ArrayList</code>；若对集合插入数据的效率要求较高并且通过下标获取集合数据较少的场景下，使用<code>LinkedList</code>；若在多线程处理集合数据时候，如果是读多写少的场景，使用<code>CopyOnWriteArrayList</code>，反之则使用<code>Vector</code>；在需要针对于集合中的元素数据实现先进后出（FILO）场景中，使用<code>Stack</code></td>
</tr>
<tr>
<td><code>Set</code></td>
<td></td>
<td>不可重复</td>
<td><code>HashSet、LinkedHashSet、TreeSet、CopyOnWriteArraySet等</code></td>
<td>针对于集合中的数据不允许存在重复的情况下使用<code>Set</code>，那么对于以下不同场景我们需要使用不同的Set进行数据的操作：若对于集合中的数据允许存在null并且数据不可重复的情况下使用<code>HashSet</code>或<code>LinkedHashSet</code>，但是如果对集合中数据的顺序有强制要求的，则需要使用<code>LinkedHashSet</code>；若对于集合中的数据不允许存在null的情况下，使用<code>TreeSet</code>；在多线程的情况下，使用<code>CopyOnWriteArraySet</code></td>
</tr>
<tr>
<td><code>Queue</code></td>
<td>有序</td>
<td>可重复</td>
<td><code>Deque、PriorityQueue、BlockingQueue</code></td>
<td>一般在需要对数据做缓冲或者并发访问的时候使用队列Queue</td>
</tr>
<tr>
<td><code>Map</code></td>
<td>无序</td>
<td>键唯一，值不唯一</td>
<td><code>HashMap、LinkedHashMap、TreeMap、HashTable等</code></td>
<td>需要保证<code>key</code>写入顺序的场景，使用<code>LinkedHashMap</code>；保证本身有序，使用<code>TreeMap</code>（需实现排序方法）；多线程使用<code>ConcurrentHashMap</code>；一般情况使用<code>HashMap</code></td>
</tr>
</tbody>
</table>
<ol>
<li>
<p>合理利用好集合的有序性（<code>sort</code>）和稳定性（<code>order</code>），避免集合的无序性（<code>unsort</code>）和不稳定性（<code>unorder</code>
）带来的负面影响。有序性是指遍历的结果是按某种比较规则依次排列的，稳定性指集合每次遍历的元素次序是一定的。如：<code>ArrayList</code>
是 <code>order / unsort</code>；<code>HashMap</code> 是 <code>unorder / unsort</code>；<code>TreeSet</code> 是 <code>order / sort</code> 。</p>
</li>
<li>
<p>判断所有集合内部的元素是否为空，使用 <code>isEmpty()</code> 方法，而不是 <code>size() == 0</code> 的方式。在某些集合中，前者的时间复杂度为 O(1)
，而且可读性更好。</p>
<blockquote>
<p>正例：</p>
</blockquote>
<pre class="hljs"><code><div>Map&lt;String, Object&gt; map = <span class="hljs-keyword">new</span> HashMap&lt;&gt;(<span class="hljs-number">16</span>);  
<span class="hljs-keyword">if</span> (map.isEmpty()) {  
    System.out.println(<span class="hljs-string">"no element in this map."</span>);  
}
</div></code></pre>
</li>
<li>
<p>在 JDK7 版本及以上，<code>Comparator</code> 实现类要满足如下三个条件，不然 <code>Arrays.sort</code>，<code>Collections.sort</code>
会抛 <code>IllegalArgumentException</code> 异常。</p>
<ul>
<li>x，y 的比较结果和 y，x 的比较结果相反。</li>
<li>x &gt; y，y &gt; z，则 x &gt; z。</li>
<li>x = y，则 x，z 比较结果和 y，z 比较结果相同。</li>
</ul>
<blockquote>
<p>反例：下例中没有处理相等的情况，交换两个对象判断结果并不互反，不符合第一个条件，在实际使用中可能会出现异常。</p>
</blockquote>
<pre class="hljs"><code><div><span class="hljs-keyword">new</span> Comparator&lt;Student&gt;() {  
    <span class="hljs-meta">@Override</span>  
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">compare</span><span class="hljs-params">(Student o1, Student o2)</span> </span>{  
        <span class="hljs-keyword">return</span> o1.getId() &gt; o2.getId() ? <span class="hljs-number">1</span> : -<span class="hljs-number">1</span>;  
    }  
}
</div></code></pre>
</li>
</ol>
<h2 id="%E5%BE%AA%E7%8E%AF%E9%81%8D%E5%8E%86">循环遍历</h2>
<blockquote>
<p>循环语句就是再满足一定条件的情况下反复执行某一个操作的语句。Java中提供了3种常用的循环语句，分别是 <code>while</code>
循环语句、<code>do...while</code> 循环语句和 <code>for</code> 循环语句。循环语句使用场景建议：</p>
</blockquote>
<table>
<thead>
<tr>
<th><strong>集合</strong></th>
<th><strong>建议使用场景</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>while</code></td>
<td>同样的操作执行多次（如：同步socket监听服务、数据库操作游标、文件内容分段读取）</td>
</tr>
<tr>
<td><code>do...while</code></td>
<td>先执行一次，然后在判断，如果条件成立，在循环执行</td>
</tr>
<tr>
<td><code>for</code></td>
<td>明确知道循环次数或需要用到索引信息的业务场景</td>
</tr>
<tr>
<td><code>foreach</code></td>
<td>默认情况下推荐使用此循环，但<code>for</code> 循环的使用场景除外</td>
</tr>
</tbody>
</table>
<ol>
<li>
<p>不要在 <code>foreach</code> 循环里进行元素的 <code>remove / add</code> 操作。<code>remove</code> 元素请使用 <code>iterator</code>
方式，如果并发操作，需要对 <code>iterator</code> 对象加锁。</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 正例：</span>
List&lt;String&gt; list = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();  
list.add(<span class="hljs-string">"1"</span>);  
list.add(<span class="hljs-string">"2"</span>);  
Iterator&lt;String&gt; iterator = list.iterator();  
<span class="hljs-keyword">while</span> (iterator.hasNext()) {  
    String item = iterator.next();  
    <span class="hljs-keyword">if</span> (删除元素的条件) {  
        iterator.remove();  
    }  
}

<span class="hljs-comment">// 反例：</span>
<span class="hljs-keyword">for</span> (String item : list) {  
    <span class="hljs-keyword">if</span> (<span class="hljs-string">"1"</span>.equals(item)) {  
        list.remove(item);  
    }  
}
</div></code></pre>
</li>
<li>
<p>循环体内，字符串的连接方式，使用 <code>StringBuilder</code> 的 <code>append</code> 方法进行扩展。</p>
<pre class="hljs"><code><div><span class="hljs-comment">// 反例：</span>
String str = <span class="hljs-string">"start"</span>;  
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">100</span>; i++) {  
    str = str + <span class="hljs-string">"hello"</span>;  
}
</div></code></pre>
<blockquote>
<p>说明：反编译出的字节码文件显示每次循环都会 <code>new</code> 出一个 <code>StringBuilder</code> 对象，然后进行 <code>append</code>
操作，最后通过 <code>toString()</code> 返回 <code>String</code> 对象，造成内存资源浪费。</p>
</blockquote>
</li>
<li>
<p>使用 <code>entrySet</code> 遍历 <code>Map</code> 类集合 KV，而不是 <code>keySet</code> 方式进行遍历。</p>
<blockquote>
<p>说明：<code>keySet</code> 其实是遍历了 2 次，一次是转为 <code>Iterator</code> 对象，另一次是从 <code>hashMap</code> 中取出 <code>key</code> 所对应的 <code>value</code>
。而<code>entrySet</code> 只是遍历了一次就把 <code>key</code> 和 <code>value</code> 都放到了 <code>entry</code> 中，效率更高。如果是 JDK8，使用 <code>Map.forEach</code> 方法。</p>
<p>正例：<code>values()</code> 返回的是 V 值集合，是一个 <code>list</code> 集合对象；<code>keySet()</code> 返回的是 K 值集合，是一个 <code>Set</code>
集合对象；<code>entrySet()</code> 返回的是 K-V 值组合的 <code>Set</code> 集合。</p>
<p>反例：由于 <code>HashMap</code> 的干扰，很多人认为 <code>ConcurrentHashMap</code> 是可以置入 <code>null</code> 值，而事实上，存储 <code>null</code> 值时会抛出 NPE
异常。</p>
</blockquote>
</li>
</ol>
<h2 id="%E9%80%92%E5%BD%92">递归</h2>
<blockquote>
<p>在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法</p>
</blockquote>
<ol>
<li>执行一个方法时，就创建一个新的受保护的独立空间(栈空间)</li>
<li>方法的局部变量是独立的，不会相互影响</li>
<li>如果方法中使用的是引用类型变量(比如数组)，就会共享该引用类型的数据.</li>
<li>递归必须向退出递归的条件逼近，否则就是无限递归,出现StackOverflowError</li>
<li>当一个方法执行完毕，或者遇到return，就会返回，遵守谁调用，就将结果返回给谁，同时当方法执行完毕或者返回时，该方法也就执行完毕。</li>
<li>递归函数中必须包含可以终止递归调用的语句。</li>
</ol>
<h2 id="%E5%88%86%E6%94%AF%E9%80%89%E6%8B%A9%E6%8E%A7%E5%88%B6">分支选择控制</h2>
<blockquote>
<p>对于要先做判断再选择的问题就要使用分支结构</p>
</blockquote>
<ol>
<li>在一个switch块内，每个case要么通过<code>continue/break/return</code>等来终止，要么注释说明程序将继续执行到哪一个case为止；</li>
<li>在一个switch块内，都必须包含一个default语句并且放在最后，即使它什么代码也没有</li>
<li>当switch括号内的变量类型为String 并且此变量为外部参数时，必须先进行非空判断。</li>
<li>在<code>if/else/for/while/do</code>语句中必须使用大括号，即使只有一行简单代码</li>
<li>表达异常的分支时，少用<code>if-else</code>方式，这种方式可以改写成：<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-keyword">if</span> (condition) {
            <span class="hljs-comment">//...</span>
            <span class="hljs-keyword">return</span> obj;
        }
        <span class="hljs-comment">// 接着写 else 的业务逻辑代码;</span>
    }
}
</div></code></pre>
</li>
<li>不要在if的判断条件中使用复杂的判断，将导致代码难以理解，应将判断结果提前赋值给一个有意义的变量</li>
<li>不要在其它表达式（尤其是条件表达式）中，使用赋值语句，比如：<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-keyword">int</span> a = <span class="hljs-number">10</span>;
        <span class="hljs-keyword">int</span> b = (a = <span class="hljs-number">20</span>) - <span class="hljs-number">1</span>;
        System.out.println(b);
    }
}
</div></code></pre>
</li>
<li>避免采用取反逻辑运算符，应尽量使用正向逻辑写法，便于理解，比如<code>if(!(a&gt;10))</code>应写为<code>if(a&lt;=10)</code></li>
<li>三目运算符<code>condition ? 表达式1 : 表达式2</code>中，高度注意表达式1和2在类型对齐时，可能抛出因自动拆箱导致的NPE异常</li>
</ol>
<h2 id="lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F">Lambda表达式</h2>
<blockquote>
<p>Lambda表达式是一个匿名函数，它可以包含表达式和语句，并且可用于创建委托或表达式目录树类型。</p>
</blockquote>
<ol>
<li>lambda只有一句时，省略大括号和return</li>
<li>在编程过程中能使用lambda表达式的就使用lambda表达式</li>
<li>链式调用应在第二个点开始在每一个点前面插入换行符<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        String str = <span class="hljs-string">""</span>;
        str = str.replace(<span class="hljs-string">"a"</span>, <span class="hljs-string">"A"</span>)
                .replace(<span class="hljs-string">"b"</span>, <span class="hljs-string">"B"</span>)
                .replace(<span class="hljs-string">"c"</span>, <span class="hljs-string">"C"</span>)
                .replace(<span class="hljs-string">"d"</span>, <span class="hljs-string">"D"</span>)
                .replace(<span class="hljs-string">"e"</span>, <span class="hljs-string">"E"</span>)
                .replace(<span class="hljs-string">"f"</span>, <span class="hljs-string">"F"</span>);
        System.out.println(str);
    }
}
</div></code></pre>
</li>
</ol>
<h2 id="%E6%97%A5%E6%9C%9F%E6%97%B6%E9%97%B4">日期时间</h2>
<ol>
<li>日期格式化时，传入pattern中表示年份统一使用小写的y。
<code>日期格式化时，yyyy表示当天所在的年，而大写的YYYY表示week in which year（JDK7之后所引入的概念），意思是当天所在的周所属的年份，一周从周日开始，周六结束，只要本周跨年，返回的YYYY就是下一年。</code></li>
<li>在日期格式化中分清楚大些的M和小写的m，大写的H和小写的h分别所指代的意义。
<ul>
<li>表示月份的是大写的M</li>
<li>表示分钟则是小写的m</li>
<li>24小时制的是大写的H</li>
<li>12小时制的是小写的h</li>
</ul>
</li>
<li>获取当前毫秒数：System.currentTimeMilis();而不是new Date().getTime();
<code>如果想获取更加精确的纳秒级的时间值，使用System.nanoTime的方式。在JDK8中，针对统计时间等场景，推荐使用Instant类。</code></li>
<li>禁止在程序的任何地方使用：java.sql.Date、java.sql.Time、java.sql.Timestamp。
<code>java.sql.Date不记录时间，getHours()抛出异常；java.sql.time不记录日期，getYear()抛出异常；java.sql.Timestamp在构造方法super((time/1000)*1000)，fastTime和nanos分开存储秒和纳秒的信息。</code></li>
<li>禁止在程序中写死一年为365天，避免在公历闰年时出现日期转换错误或程序逻辑错误，详情请参考如下例子：</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// 正例：</span>
<span class="hljs-comment">// 获取今年的天数</span>
<span class="hljs-keyword">int</span> daysOfThisYear=LocalDate.now().lengthOfYear();

<span class="hljs-comment">// 获取指定某年的天数</span>
        LocalDate.of(<span class="hljs-number">2023</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>).lengthOfYear();

<span class="hljs-comment">// 反例：</span>
<span class="hljs-comment">// 第一种情况：在闰年366天时，会出现数组下标越界的问题</span>
        <span class="hljs-keyword">int</span>[]dayArray=<span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[<span class="hljs-number">365</span>];

<span class="hljs-comment">// 第二种情况：一年有效期的授权，例如今年1月17日进行授权，硬编码365天得到的授权到期日期却是1月16日</span>
        Calendar calendar=Calendar.getInstance();
        calendar.set(<span class="hljs-number">2023</span>,<span class="hljs-number">1</span>,<span class="hljs-number">17</span>);
        calendar.add(Calendar.DATE,<span class="hljs-number">365</span>);
</div></code></pre>
<h2 id="%E8%B5%84%E6%BA%90%E9%87%8A%E6%94%BE">资源释放</h2>
<ol>
<li>正确理解java中的资源
<code>java中所谓的资源就是实现了Closeable或AutoCloseable接口的类及其子类对象，例如输入流InputStream、输出流OutputStream以及数据库连接等</code></li>
<li>使用try-catch-finally以及try-with-resource任意一种进行资源的释放（<strong>能使用<code>try-with-resource</code>
的情况下必须使用<code>try-with-resource</code></strong>）
<code>JDK7以前只能使用try-catch-finally来完成资源的释放；在JDK7以后try-catch-finally与try-with-resource二者选其一即可。</code><pre class="hljs"><code><div> <span class="hljs-comment">// try-catch-finally示例：</span>

 InputStream in = <span class="hljs-keyword">null</span>;
 OutputStream out = <span class="hljs-keyword">null</span>;
 <span class="hljs-keyword">try</span> {
     in = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"E:\\2022.7.16.mp4"</span>);
     out = <span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"E:\\new.mp4"</span>);
     <span class="hljs-keyword">byte</span>[] buffer = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[<span class="hljs-number">1024</span>];
     <span class="hljs-keyword">int</span> len;
     <span class="hljs-keyword">while</span> ((len = in.read(buffer)) != -<span class="hljs-number">1</span>) {
         out.write(buffer, <span class="hljs-number">0</span>, len);
     }
 } <span class="hljs-keyword">catch</span> (Exception ex) {
     log.error(<span class="hljs-string">"xxxxxx"</span>);
 } <span class="hljs-keyword">finally</span> {
     <span class="hljs-keyword">if</span> (in != <span class="hljs-keyword">null</span>) {
         in.close();
     }
     <span class="hljs-keyword">if</span> (out != <span class="hljs-keyword">null</span>) {
         out.close();
     }
 }

 <span class="hljs-comment">// try-with-resource示例：</span>

 <span class="hljs-comment">// JDK7、JDK8</span>
 <span class="hljs-keyword">try</span> (InputStream in = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"E:\\2022.7.16.mp4"</span>);
      OutputStream out = <span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"E:\\new.mp4"</span>);) {
     <span class="hljs-keyword">byte</span>[] buffer = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[<span class="hljs-number">1024</span>];
     <span class="hljs-keyword">int</span> len;
     <span class="hljs-keyword">while</span> ((len = in.read(buffer)) != -<span class="hljs-number">1</span>) {
         out.write(buffer, <span class="hljs-number">0</span>, len);
     }
 } <span class="hljs-keyword">catch</span> (Exception ex) {
     log.error(<span class="hljs-string">"xxxxxx"</span>);
 }
</div></code></pre>
</li>
<li>当流以及连接等资源使用完成后必须立即进行对应的释放</li>
</ol>
<h2 id="%E4%BA%8B%E5%8A%A1">事务</h2>
<h3 id="%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1">声明式事务</h3>
<ol>
<li>
<p>正确理解@Transactional注解的常用属性：</p>
<ul>
<li></li>
</ul>
<p>propagation：事务的传播，也就是多事务之间调用的时候做出的一些策略，例如a方法调用b方法，存在两个常用的参数：REQUIRES（如果a方法有事务，b方法没有事务，那么b会跟着a事务进行运行；如果a没有事务，b有事务，那么会去创建一个新的事务）；REQUIRES_NEW（无论a、b方法是否存在事务，都会去创建一个新的事务）。</p>
<ul>
<li>isolation：隔离级别，也就是数据库的四种隔离级别</li>
<li>timeout：事务的超时时间，开启事务到提交事务的时间，默认是-1永不超时，如果设置了超时时间(单位秒)
，那么如果超过该时间限制了但事务还没有完成，则自动回滚事务。</li>
<li>readOnly：是否只读，默认是false，若为true，则不能进行增、删、改。</li>
<li>rollbackFor：指定哪些异常需要进行回滚，默认为RuntimeException。</li>
<li>noRollbackFor：指定哪些异常不进行回滚。</li>
</ul>
</li>
<li>
<p>禁止在类上面添加事务注解@Transactional
<code>当事务作用于类上，那么当我们执行该类中的所有方法时，均会开启一个事务，造成不必要的资源浪费，所以说我们需要做到事务的精准控制。</code></p>
</li>
<li>
<p>对于所有存在多个增、删、改操作的方法必须添加事务控制</p>
</li>
<li>
<p>@Transactional只能被应用到public方法上，对于其他非public的方法，如果标记了@Transactional也不会报错，但该方法的事务业不会起到任何作用</p>
</li>
<li>
<p>使用<code>org.springframework.transaction.annotation.Transactional</code>来进行做事务的控制</p>
</li>
<li>
<p>在<code>@Transactional</code>中必须手动指定事务回滚策略<code>rollbackFor = Exception.class</code></p>
</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// 示例</span>
<span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">XxxxService</span> </span>{
    <span class="hljs-meta">@Resource</span>
    <span class="hljs-keyword">private</span> XxxxMapper xxxxMapper;

    <span class="hljs-meta">@Transactional</span>(rollbackFor = Exception<span class="hljs-class">.<span class="hljs-keyword">class</span>)
    <span class="hljs-title">public</span> <span class="hljs-title">String</span> <span class="hljs-title">a</span>(<span class="hljs-title">XxxxDTO</span> <span class="hljs-title">xxxxDTO</span>) </span>{
        <span class="hljs-comment">// ...</span>
        xxxxMapper.addXxxx(xxxx);
    }
}
</div></code></pre>
<ol start="7">
<li>service中的方法a去调用方法b时，a方法没有事务，b方法存在事务，那么必须先在service中获取到当前service所对应的代理对象再去调用对应的b方法。
<code>Spring中的事务的底层是基于AOP去实现的，其中所有的事物控制的逻辑会存在于service的代理对象的代理逻辑中，如果需要Spring的事物生效，那么去调用存在事务的方法就必须是通过代理对象去进行调用。</code></li>
</ol>
<pre class="hljs"><code><div><span class="hljs-comment">// 示例</span>
<span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">XxxxService</span> </span>{
    <span class="hljs-meta">@Resource</span>
    ApplicationContext applicationContext;

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">a</span><span class="hljs-params">(XxxxDTO xxxxDTO)</span> </span>{
        <span class="hljs-comment">// ...</span>
        XxxxService xxxxService = applicationContext.getBean(XxxxService<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
        xxxxService.b(xxxxDTO);
    }

    <span class="hljs-meta">@Transactional</span>(rollbackFor = Exception<span class="hljs-class">.<span class="hljs-keyword">class</span>)
    <span class="hljs-title">public</span> <span class="hljs-title">String</span> <span class="hljs-title">b</span>(<span class="hljs-title">XxxxDTO</span> <span class="hljs-title">xxxxDTO</span>) </span>{
        <span class="hljs-comment">// ...</span>
        xxxxMapper.addXxxx(xxx);
    }
}
</div></code></pre>
<h3 id="%E7%BC%96%E7%A8%8B%E5%BC%8F%E4%BA%8B%E5%8A%A1">编程式事务</h3>
<ol>
<li>使用TransactionTemplate对象实现编程式事务</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{
    <span class="hljs-comment">// TransactionTemplate示例</span>
    <span class="hljs-meta">@Resource</span>
    <span class="hljs-keyword">private</span> TransactionTemplate transactionTemplate;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">add</span><span class="hljs-params">(UserInfo userInfo)</span> </span>{
        <span class="hljs-comment">// ...</span>
        <span class="hljs-comment">// 开启事务</span>
        <span class="hljs-keyword">return</span> transactionTemplate.execute(status -&gt; {
            <span class="hljs-keyword">int</span> result = userMapper.add(userInfo);
            <span class="hljs-keyword">return</span> result;
        });
    }
}
</div></code></pre>
<h2 id="%E5%B7%A5%E5%85%B7%E7%B1%BB">工具类</h2>
<blockquote>
<p>工具善其事，必先利其器</p>
</blockquote>
<h3 id="%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7%E5%8C%85">常用工具包</h3>
<ol>
<li>官方工具类（<code>java.lang.Math</code>、<code>java.util.Collections</code>）</li>
<li>权威工具包（apache、google、guava）</li>
<li>社区工具包（hutool）</li>
<li>公司内部工具包（futool）</li>
</ol>
<blockquote>
<p>工具类是日常工作开发不同项目复用概率最高的部分，而复用最好的方式是引入依赖包而非拷贝代码；因此，平时工作应该习惯于收集和整理工具类代码；同时，优秀的工具类代码，都应该被整理并收录进公共的包中，以方便今后的自己以及其他团队成员；</p>
</blockquote>
<h3 id="%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%8F%8A%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF">常用工具类及使用场景</h3>
<table>
<thead>
<tr>
<th><strong>场景</strong></th>
<th><strong>package</strong></th>
<th><strong>Class.method(...)</strong></th>
<th><strong>功能描述</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>字符串</td>
<td>cn.hutool.core.util</td>
<td>StrUtil.isBlank</td>
<td>判断字符串为空或空白字符串</td>
</tr>
<tr>
<td>时间</td>
<td>cn.hutool.core.date</td>
<td>DateUtil.parse</td>
<td>尝试将各种格式的时间字符串转换为时间对象</td>
</tr>
<tr>
<td>文件</td>
<td>cn.hutool.core.io</td>
<td>FileUtil.writeString</td>
<td>将字符串以指定编码写入目标文件中</td>
</tr>
<tr>
<td>zip</td>
<td>cn.hutool.core.util</td>
<td>ZipUtil.zip</td>
<td>压缩指定文件夹</td>
</tr>
<tr>
<td>集合</td>
<td>cn.hutool.core.collection</td>
<td>CollUtil.isEmpty</td>
<td>判断集合是否为null或size等于0</td>
</tr>
<tr>
<td>excel</td>
<td>cn.hutool.poi.excel</td>
<td>ExcelUtil.getReader</td>
<td>读取excel文件内容</td>
</tr>
<tr>
<td>json</td>
<td>com.alibaba.fastjson</td>
<td>JSON.toJSONString</td>
<td>对象转为json字符串</td>
</tr>
<tr>
<td>xml</td>
<td>cn.hutool.core.util</td>
<td>XmlUtil.parseXml</td>
<td>读取xml内容</td>
</tr>
<tr>
<td>word</td>
<td>cn.hutool.poi.word</td>
<td>WordUtil.getWriter</td>
<td>创建word文档写对象</td>
</tr>
<tr>
<td>pdf</td>
<td>com.itextpdf.text.pdf</td>
<td>PdfReader</td>
<td>pdf读取类</td>
</tr>
<tr>
<td>加解密</td>
<td>cn.hutool.core.codec</td>
<td>Base64.encode</td>
<td>Base64工具类，提供Base64的编码和解码方案</td>
</tr>
<tr>
<td>缓存</td>
<td>cn.hutool.cache</td>
<td>CacheUtil.newLFUCache</td>
<td>创建一个缓存容器</td>
</tr>
<tr>
<td>ofd</td>
<td>cn.hutool.poi.ofd</td>
<td>OfdWriter</td>
<td>ofd文档生成器类</td>
</tr>
<tr>
<td>图片</td>
<td>cn.hutool.core.img</td>
<td>ImgUtil.convert</td>
<td>转换图片格式</td>
</tr>
<tr>
<td>io</td>
<td>cn.hutool.core.io</td>
<td>IoUtil.copy</td>
<td>流对拷</td>
</tr>
<tr>
<td>socket</td>
<td>cn.hutool.socket</td>
<td>SocketUtil.connect</td>
<td>套接字远程连接</td>
</tr>
<tr>
<td>websocket</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>sse</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<blockquote>
<p>小技巧：在特定领域场景下的操作，可以先尝试看一下是否存在工具类 <strong>特定领域+Util</strong> 存在，如果有，则可以直接使用；</p>
</blockquote>
<h3 id="%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9">如何选择</h3>
<ol>
<li>学会查询各种帮助文档，工具的熟练离不开大量的使用；</li>
<li>走查别人代码时，注意别人在当前场景下的工具类的选择和使用；</li>
<li>百度查询时注意采用找标准工具类使用的解决方案；</li>
<li>不要害羞和想太多，问问周围人有没有现成的工具类可用；</li>
<li>确认没有现成的，尝试自己封装；</li>
</ol>
<h3 id="%E5%B7%A5%E5%85%B7%E7%B1%BB%E4%B8%A5%E6%A0%BC%E5%AE%9A%E4%B9%89">工具类严格定义</h3>
<ol>
<li>防止被继承</li>
<li>私有构造函数</li>
<li>构造函数抛错</li>
</ol>
<pre class="hljs"><code><div><span class="hljs-keyword">package</span> java.lang;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Math</span> </span>{
    <span class="hljs-comment">/**
     * Don't let anyone instantiate this class.  
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">Math</span><span class="hljs-params">()</span> </span>{
    }
} 
</div></code></pre>
<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UtilsClass</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">UtilsClass</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Error(<span class="hljs-string">"不要实例化我！"</span>);
    }
}
</div></code></pre>
<blockquote>
<p>Spring项目中给工具类加上@Component注解，并将配置注入进工具类，而后将工具类以Bean对象的方式注入进服务中使用的用法是不正确的用法；</p>
</blockquote>
<h3 id="%E6%96%B9%E6%B3%95%E9%87%8D%E8%BD%BD">方法重载</h3>
<blockquote>
<p>举例：文件操作场景下，String、File、Path都代表文件或文件夹，则工具类方法提供重载的情况下，能最大程度避免业务代码中的数据转换问题，能让业务代码更简单整洁</p>
</blockquote>
<pre class="hljs"><code><div><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TfFileUtils</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">TfFileUtils</span><span class="hljs-params">()</span> </span>{
    }

    <span class="hljs-comment">/**
     * 判断文件夹是否为空
     *
     * <span class="hljs-doctag">@param</span> directory 文件夹
     * <span class="hljs-doctag">@return</span> true：为空；false：不为空；
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isEmpty</span><span class="hljs-params">(Path directory)</span> </span>{
        <span class="hljs-comment">// 核心方法</span>
        <span class="hljs-keyword">try</span> (DirectoryStream&lt;Path&gt; ds = Files.newDirectoryStream(directory)) {
            <span class="hljs-keyword">for</span> (Path ignored : ds) {
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
            }
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
        } <span class="hljs-keyword">catch</span> (NotDirectoryException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(
                    <span class="hljs-string">"判断目录是否为空错误，目标不是目录："</span> + directory, e);
        } <span class="hljs-keyword">catch</span> (SecurityException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(
                    <span class="hljs-string">"判断目录是否为空错误，没有权限："</span> + directory, e);
        } <span class="hljs-keyword">catch</span> (IOException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(
                    <span class="hljs-string">"判断目录是否为空错误："</span> + directory, e);
        }
    }

    <span class="hljs-comment">/**
     * 判断文件夹是否为空
     *
     * <span class="hljs-doctag">@param</span> directory 文件夹
     * <span class="hljs-doctag">@return</span> true：为空；false：不为空；
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isEmpty</span><span class="hljs-params">(String directory)</span> </span>{
        <span class="hljs-comment">// 参数重载方法</span>
        <span class="hljs-keyword">return</span> isEmpty(Paths.get(directory));
    }

    <span class="hljs-comment">/**
     * 判断文件夹是否为空
     *
     * <span class="hljs-doctag">@param</span> directory 文件夹
     * <span class="hljs-doctag">@return</span> true：为空；false：不为空；
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isEmpty</span><span class="hljs-params">(File directory)</span> </span>{
        <span class="hljs-comment">// 参数重载方法</span>
        <span class="hljs-keyword">return</span> isEmpty(directory.toPath());
    }
}
</div></code></pre>
<h3 id="%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%BC%82%E5%B8%B8">工具类异常</h3>
<p>工具类异常不允许通过日志框架进行打印，应该将精准详细的异常抛给调用者，由调用者来决定如何处理；</p>
<blockquote>
<p>异常有时也是业务的一环，内部强行打印会影响业务代码的日志输出与问题排查；</p>
</blockquote>
<h2 id="%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95">单元测试</h2>
<blockquote>
<p>单元测试（unit testing），是指对软件中的最小可测试单元进行检查和验证。至于“单元”的大小或范围，并没有一个明确的标准，“单元”可以是一个函数、方法、类、功能模块或者子系统。</p>
</blockquote>
<h3 id="%E6%84%8F%E4%B9%89">意义</h3>
<p>通常开发都是以完成任务为主要目标（正向开发）:<strong>正确输入</strong> 得到 <strong>正确输出</strong>；往往忽略了:<strong>错误输入</strong> 应该得到 **正确的错误输出
**；测试的目的即在于此，测试用例是包含了反向测试；</p>
<blockquote>
<p>小技巧：任何未实现的分支逻辑，都用 <strong>未实现异常</strong> 代替，并加上 <strong>TODO</strong> 说明，以免出现开发漏掉的情况，和引起后续未知异常。</p>
</blockquote>
<h3 id="%E6%AD%A3%E7%A1%AE%E8%AE%A4%E8%AF%86">正确认识</h3>
<blockquote>
<p>人非圣贤，孰能无过，再优秀的开发人员也会可能写出有BUG的代码。</p>
</blockquote>
<p>测试用例不是负担，而是在高质量代码要求下的程序员的自我保护手段；</p>
<h3 id="%E6%96%B9%E6%B3%95">方法</h3>
<ol>
<li>
<p>好的单元测试必须遵守AIR 原则。
说明：单元测试在线上运行时，感觉像空气（AIR）一样感觉不到，但在测试质量的保障上，却是非常关键的。好的单元测试宏观上来说，具有自动化、独立性、可重复执行的特点。</p>
<ul>
<li>A：Automatic（自动化）</li>
<li>I：Independent（独立性）</li>
<li>R：Repeatable（可重复）</li>
</ul>
</li>
<li>
<p>单元测试应该是全自动执行的，并且非交互式的。测试用例通常是被定期执行的，执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。不允许使用
System.out 来进行人肉验证，单元测试必须使用 assert 来验证。</p>
</li>
<li>
<p>保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护，单元测试用例之间决不能互相调用，也不能依赖执行的先后次序。
反例：method2 需要依赖 method1 的执行，将执行结果作为 method2 的输入。</p>
</li>
<li>
<p>单元测试是可以重复执行的，不能受到外界环境的影响。
说明：单元测试通常会被放到持续集成中，每次有代码 push 时单元测试都会被执行。如果单测对外部环境（网络、服务、中间件等）有依赖，容易导致持续集成机制的不可用。
正例：为了不受外界环境影响，要求设计代码时就把 SUT（Software under test）的依赖改成注入，在测试时用 Spring
这样的 DI 框架注入一个本地（内存）实现或者 Mock 实现。</p>
</li>
<li>
<p>对于单元测试，要保证测试粒度足够小，有助于精确定位问题。单测粒度至多是类级别，一般是方法级别。
说明：测试粒度小才能在出错时尽快定位到出错的位置。单元测试不负责检查跨类或者跨系统的交互逻辑，那是集成测试的领域。</p>
</li>
<li>
<p>核心业务、核心应用、核心模块的增量代码确保单元测试通过。
说明：新增代码及时补充单元测试，如果新增代码影响了原有单元测试，请及时修正。</p>
</li>
<li>
<p>单元测试代码必须写在如下工程目录： src/test/java，不允许写在业务代码目录下。
说明：源码编译时会跳过此目录，而单元测试框架默认是扫描此目录。</p>
</li>
<li>
<p>单测的基本目标：语句覆盖率达到 70%；核心模块的语句覆盖率和分支覆盖率都要达到 100%
说明：在工程规约的应用分层中提到的 DAO 层，Manager 层，可重用度高的 Service，都应该进行单元测试。</p>
</li>
<li>
<p>编写单元测试代码遵守 BCDE 原则，以保证被测试模块的交付质量。</p>
<ul>
<li>B：Border，边界值测试，包括循环边界、特殊取值、特殊时间点、数据顺序等。</li>
<li>C：Correct，正确的输入，并得到预期的结果。</li>
<li>D：Design，与设计文档相结合，来编写单元测试。</li>
<li>E：Error，强制错误信息输入（如：非法数据、异常流程、业务允许外等），并得到预期的结果。</li>
</ul>
</li>
<li>
<p>对于数据库相关的查询，更新，删除等操作，不能假设数据库里的数据是存在的，或者直接操作数据库把数据插入进去，请使用程序插入或者导入数据的方式来准备数据。</p>
<blockquote>
<p>反例：删除某一行数据的单元测试，在数据库中，先直接手动增加一行作为删除目标，但是这一行新增数据并不符合业务插入规则，导致测试结果异常。</p>
</blockquote>
</li>
<li>
<p>和数据库相关的单元测试，可以设定自动回滚机制，不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。</p>
<blockquote>
<p>正例：在基础技术部的内部单元测试中，使用 FOUNDATION_UNIT_TEST_的前缀来标识单元测试相关代码。</p>
</blockquote>
</li>
<li>
<p>对于不可测的代码在适当的时机做必要的重构，使代码变得可测避免为了达到测试要求而书写不规范测试代码。</p>
</li>
<li>
<p>在设计评审阶段，开发人员需要和测试人员一起确定单元测试范围，单元测试最好覆盖所有测试用例（UC）。</p>
</li>
<li>
<p>单元测试作为一种质量保障手段，在项目提测前完成单元测试，不建议项目发布后补充单元测试用例。</p>
</li>
<li>
<p>为了更方便地进行单元测试，业务代码应避免以下情况：</p>
<ul>
<li>构造方法中做的事情过多。</li>
<li>存在过多的全局变量和静态方法。</li>
<li>存在过多的外部依赖。</li>
<li>存在过多的条件语句。
说明：多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。</li>
</ul>
</li>
<li>
<p>不要对单元测试存在如下误解：</p>
<ul>
<li>那是测试同学干的事情。本文是开发手册，凡是本文内容都是与开发同学强相关的。</li>
<li>单元测试代码是多余的。系统的整体功能与各单元部件的测试正常与否是强相关的。</li>
<li>单元测试代码不需要维护。一年半载后，那么单元测试几乎处于废弃状态。</li>
<li>单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。</li>
</ul>
</li>
</ol>
<h2 id="sql">SQL</h2>
<ol>
<li>禁止使用属性隐式转换；
<ul>
<li>强制类型转换会全表扫描。</li>
</ul>
</li>
<li>禁止在<code>WHERE</code>条件的属性上使用函数或者表达式；
<ul>
<li>列使用函数会避开索引导致全表扫描。</li>
</ul>
</li>
<li>如果明确知道只有一条结果返回，limit 1能够提高效率；
<blockquote>
<p>参考 java stream 的<code>first</code>，而不是获取<code>List</code>后取第一个；</p>
</blockquote>
</li>
</ol>
<h2 id="%E7%AD%89%E5%80%BC%E6%AF%94%E8%BE%83">等值比较</h2>
<ol>
<li>
<p>基础类型比较相等使用==，比较大小使用&gt;、&lt;</p>
</li>
<li>
<p>针对浮点型，使用Math.abs(a - b) &lt; 1e-6进行相等比较</p>
<blockquote>
<p>浮点型计算存在精度丢失问题，会导致计算后的实际结果不完全等于0，通过精度来进行判断</p>
</blockquote>
</li>
<li>
<p>基础类型的包装类型比较相等使用equals</p>
</li>
<li>
<p>对象比较相等使用ObjectUtil.equals</p>
</li>
</ol>
<h2 id="%E6%89%B9%E9%87%8F%E6%8F%90%E4%BA%A4">批量提交</h2>
<ol>
<li>针对大数据量的新增、修改、删除，需使用批量提交模式</li>
<li>批量提交模式必须在事务内，保证批量提交的一致性</li>
<li>批量提交时，定义Sqlsession使用try-with-resource的方式，自动关闭<pre class="hljs"><code><div><span class="hljs-keyword">try</span> (SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH)) {
}
</div></code></pre>
</li>
<li>批量事务模式中不允许执行查询</li>
<li>批量事务模式中不允许使用外部注入mapper方式执行查询（事务不同步）</li>
<li>批量事务模式，按BATCH_COUNT（500，定义为全局静态变量）一批分批提交<pre class="hljs"><code><div> SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
 <span class="hljs-keyword">try</span> (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
     JsjlMapper jsjlMapper = sqlSession.getMapper(JsjlMapper<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
     <span class="hljs-keyword">int</span> i = <span class="hljs-number">1</span>;
     <span class="hljs-keyword">int</span> size = jsjlModelList.size();
     <span class="hljs-keyword">int</span> idxLimit = Math.min(BATCH_COUNT, size);
     <span class="hljs-keyword">for</span> (JsjlModel jsjlModel : jsjlModelList) {
         jsjlMapper.insert(jsjlModel);
         <span class="hljs-keyword">if</span> (i == idxLimit) {
             sqlSession.flushStatements();
             idxLimit = Math.min(idxLimit + BATCH_COUNT, size);
         }
         i++;
     }
 }
</div></code></pre>
</li>
</ol>
<h2 id="%E5%85%AC%E5%8F%B8%E5%9F%BA%E7%A1%80%E7%BB%84%E4%BB%B6%E6%B8%85%E5%8D%95">公司基础组件清单</h2>
<p><strong>【20230521修改】</strong></p>
<table>
<thead>
<tr>
<th>组件名</th>
<th>依赖</th>
<th>补充说明</th>
<th>版本号</th>
</tr>
</thead>
<tbody>
<tr>
<td>对象存储</td>
<td>tf-spring-boot-storage-dxcc-starter</td>
<td>针对存储路径+文件名的操作</td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>文件服务</td>
<td>tf-spring-boot-storage-wjfw-starter</td>
<td>针对文件编号的文件操作</td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>上传插件</td>
<td>tf-spring-boot-storage-plugin-fileupload</td>
<td>MultiPartFile直通存储，避开应用磁盘</td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>本地存储驱动</td>
<td>tf-spring-boot-storage-dxcc-driver-local</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>sftp存储驱动</td>
<td>tf-spring-boot-storage-dxcc-driver-sftp</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>ftp存储驱动</td>
<td>tf-spring-boot-storage-dxcc-driver-ftp</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>cifs存储驱动</td>
<td>tf-spring-boot-storage-dxcc-driver-cifs</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>天翼云对象存储驱动</td>
<td>tf-spring-boot-storage-dxcc-driver-ctoos</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>阿里云对象存储驱动</td>
<td>tf-spring-boot-storage-dxcc-driver-aloss</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>华为云对象存储驱动</td>
<td>tf-spring-boot-storage-dxcc-driver-hwobs</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>控制台日志</td>
<td>tf-spring-boot-component-console-log</td>
<td>http://ip:port/_logs</td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>自动建表</td>
<td>tf-spring-boot-component-ibatis-create-table</td>
<td>配合其他有配套运行表需要的组件工作，让组件真正做到引入即可用</td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>系统字段自动维护</td>
<td>tf-spring-boot-component-ibatis-db-sys-field</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>mybatis增强</td>
<td>tf-spring-boot-component-ibatis-plus</td>
<td>目前主要增强的是mybatis batch提交操作，让批提交简单好用</td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>java环境变量设置</td>
<td>tf-spring-boot-component-java-property-set</td>
<td>用于一些需要进行环境变量设置的，避免硬编码</td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>http组件</td>
<td>tf-spring-boot-component-rest-template-plus</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>接口日志</td>
<td>tf-spring-boot-component-web-http-request-log</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>2.0模拟mq消息</td>
<td>tf-api-mq-xxjl</td>
<td>在mq消息对接不便时的替代方案，包括了开发场景和现场运行场景皆可</td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>单源万能接口</td>
<td>tf-mybatis-plus-plugin-route-starter</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>多源万能接口</td>
<td>tf-mybatis-plus-plugin-route-multi-source-starter</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>安全网关(csb)万能接口</td>
<td>tf-mybatis-plus-plugin-route-csb-starter</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>拉模式任务组件</td>
<td>tf-spring-boot-component-task-pull-starter</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>推模式任务组件</td>
<td>tf-spring-boot-component-task-push-starter</td>
<td></td>
<td>1.0.0-SNAPSHOT</td>
</tr>
<tr>
<td>版本信息gradle插件</td>
<td>bbinfo-plugin</td>
<td></td>
<td>1.0.1</td>
</tr>
<tr>
<td>数据库版本管理</td>
<td>sjkbbgl-core</td>
<td></td>
<td>1.1.0</td>
</tr>
<tr>
<td>swx工具包</td>
<td>swxtool</td>
<td></td>
<td>1.0.1-SNAPSHOT</td>
</tr>
<tr>
<td>excel导出</td>
<td>office-spring-boot-starter</td>
<td></td>
<td>2.0.1</td>
</tr>
</tbody>
</table>
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
//是否显示导航栏
var showNavBar = true;
//是否展开导航栏
var expandNavBar = true;
var currentIndex = 0;
var currentScrollHigh = 0;
var currentContentScrollHigh = 0;
function sleep(numberMillis) {
var now = new Date();
var exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return;
}
}
 

$(window).onbeforeunload = function(){
currentIndex = 0;
}


$(window).load(function(){
var h1s = $("body").find("h1");
var h2s = $("body").find("h2");
var h3s = $("body").find("h3");
var h4s = $("body").find("h4");
var h5s = $("body").find("h5");
var h6s = $("body").find("h6");

var headCounts = [h1s.length, h2s.length, h3s.length, h4s.length, h5s.length, h6s.length];
var vH1Tag = null; // 显示的最高层级
var vH2Tag = null; // 显示的最低层级
var sum = 0;


for(var i = 0; i < headCounts.length; i++){
if(headCounts[i] > 0){
for( var y = 0; y < headCounts[i]; y++)
sum = sum + 1;
}
}
for(var i = 0; i < headCounts.length; i++){
if(headCounts[i] > 0){
if(vH1Tag == null){
vH1Tag = 'h' + (i + 1);
}else{
vH2Tag = 'h' + (i + 1);
}
}
}
if(vH1Tag == null){
return;
}

$("body").prepend('<div class="BlogAnchor">' +
'<span style="color:red;position:absolute;top:-6px;left:0px;cursor:pointer;" οnclick="$(\'.BlogAnchor\').hide();">×</span>' +
'<p>' +
'<b id="AnchorContentToggle" title="收起" style="cursor:pointer;">目录▲</b>' +
'</p>' +
'<div class="AnchorContent" id="AnchorContent"> </div>' +
'</div>' );

var vH1Index = 0;
var vH2Index = 0;


var vIndexH1 = 0;
var vIndexH2 = 0;
var vIndexH3 = 0;
var vIndexH4 = 0;
var vIndexH5 = 0;
var vIndexH6 = 0;
var headerALL = [];
var headerIDALL = [];
var headerHightALL = [];
$("body").find("h1,h2,h3,h4,h5,h6").each(function(i,item){

var id = '';
var name = '';
var tag = $(item).get(0).tagName.toLowerCase();
var className = '';
// i=0 tag=h1 i=1 tag=h2 i=2 tag=h2

if(tag == "h1"){
id = name;
vIndexH2 = 0;
vIndexH3 = 0;
vIndexH4 = 0;
vIndexH5 = 0;
vIndexH6 = 0;
className = 'item_h1';

//alert("tag ="+ tag +"---- i = "+ i + " name ="+ name +" className= "+ className);
//tag =h1---- i = 0 name =1 className= item_h1
}else if(tag == "h2"){

id = vIndexH1 + '_' + ++vIndexH2;
name = vIndexH1 + '.' + vIndexH2;
className = 'item_h2';
vIndexH3 = 0;
vIndexH4 = 0;
vIndexH5 = 0;
vIndexH6 = 0;

}else if(tag == "h3"){
id = vIndexH1 + '_' + vIndexH2+ '_' + ++vIndexH3;
name = vIndexH1 + '.' + vIndexH2+ '.' + +vIndexH3;
className = 'item_h3';
vIndexH4 = 0;
vIndexH5 = 0;
vIndexH6 = 0;

}

else if(tag == "h4"){
id = vIndexH1 + '_' + vIndexH2+ '_' +vIndexH3 +'_'+ ++vIndexH4 ;
name = vIndexH1 + '.' + vIndexH2+ '.' +vIndexH3 +'.'+ vIndexH4 ;
className = 'item_h4';
vIndexH5 = 0;
vIndexH6 = 0;
}

else if(tag == "h5"){
id = vIndexH1 + '_' + vIndexH2+ '_' +vIndexH3 +'_' +vIndexH4+'_'+ ++vIndexH5;
name = vIndexH1 + '.' + vIndexH2+ '.' +vIndexH3 +'.' +vIndexH4+'.'+ vIndexH5;
className = 'item_h5';
vIndexH6 = 0;
}

else if(tag == "h6"){
id = vIndexH1 + '_' + vIndexH2+ '_' +vIndexH3 +'_' +vIndexH4+'_' +vIndexH5+'_'+ ++vIndexH6;
name = vIndexH1 + '.' + vIndexH2+ '.' +vIndexH3 +'.' +vIndexH4+'.' +vIndexH5+'.'+ vIndexH6;
className = 'item_h6';

}
if(name.length > 2){

var mFirstName = name.substring(0,2);
while(mFirstName == "0."){
name = name.substring(2,name.length);
mFirstName = name.substring(0,2);
}

}
$(item).attr("id","wow"+id+"_index_"+i);
$(item).addClass("wow_head");
var itemHeight = $(item).offset().top
$("#AnchorContent").css('max-height', ($(document).height()) + 'px');
$("#AnchorContent").css('height', ($(window).height()) + 'px');
$("#AnchorContent").css('overflow','auto');
// $("#AnchorContent").append('<li><a class="nav_item '+className+' anchor-link" href="#wow'+id+'_index_'+i+'" link="#wow'+id+'" index="'+i+'">'+name+" "+$(this).text()+" "+'</a></li>');
$("#AnchorContent").append('<li><a class="nav_item '+className+' anchor-link" href="#wow'+id+'_index_'+i+'" link="#wow'+id+'" index="'+i+'">'+" "+$(this).text()+" "+'</a></li>');
var str = "#wow"+id+"_index_"+i;
headerALL.push($(item));
headerIDALL.push(str);
// console.log(" i = "+ i +" id =" + id +" itemHeight = "+ itemHeight);
});

// 1
// 1.1
// 1.1.1
// 1.1.1.1
// 1.1.1.1.1
// 1.1.1.1.1.1
$("#AnchorContentToggle").click(function(){
var text = $(this).html();
if(text=="目录▲"){
$(this).html("目录▼");
$(this).attr({"title":"展开"});
}else{
$(this).html("目录▲");
$(this).attr({"title":"收起"});
}
$("#AnchorContent").toggle();
});


$(".anchor-link").click(function(){

//$("html,body").animate({scrollTop: $($(this).attr("link")).offset().top}, 10);
var index = $(this).attr("index");
$(".BlogAnchor li .nav_item.current").removeClass('current');
$(headerNavs[index]).addClass('current');
var scrollTop = $(window).scrollTop(); // 获得将要到达的点离顶距离
currentScrollHigh = scrollTop;
currentContentScrollHigh = headerHightALL[index];
var value = headerTops[index];
currentIndex = value;
console.log("index = "+ index+ " headerTops["+index+"] ="+ value + "scrollTop="+ scrollTop);
});

var headerNavs = $(".BlogAnchor li .nav_item");
var headerTops = [];
var mHeight = 0;
$(".wow_head").each(function(i, n){
var value = $(n).offset().top;

headerTops.push($(n).offset().top);
console.log("i = "+ i+ " offset().top ="+ value);
});
headerTops.push($(document).height());

window.onresize = function(){

headerTops = [];
$.each(headerNavs, function(i, n){
$(n).trigger("click");
document.querySelector(headerIDALL[i]).scrollIntoView(true);
//var high = $(n).offset().top;
var scrollTop = $(window).scrollTop();
headerTops.push(scrollTop);
console.log("headerNavs_index="+i +" scrollTop="+scrollTop +" headerTops="+headerTops[i]);
}
);
headerTops.push($(document).height());

//$("#AnchorContent").height($(window).height());
$("#AnchorContent").css('height', ($(window).height()) + 'px');
var xcontentWidth = $("#AnchorContent").width();
var xWidth = $(window).width();
var xlength = xWidth - xcontentWidth;
$("body").css("marginLeft",xcontentWidth+'px');
$("body").css("max-width",xlength);

$(headerNavs[currentIndex]).trigger("click");
document.querySelector(headerIDALL[i]).scrollIntoView(true);
}
$(window).scroll(function(){
var scrollTop = $(window).scrollTop(); // 获得将要到达的点离顶距离

$.each(headerTops, function(i, n){
if( (scrollTop >= (headerTops[i]) && scrollTop < (headerTops[i+1] -1)) ){
console.log("headerTops[i-1]"+headerTops[i-1]+"headerTops[i]"+headerTops[i]+" scrollTop ="+ scrollTop+" headerTops[i+1]= "+headerTops[i+1] +" i ="+i);
// $(".BlogAnchor li .nav_item.current").removeClass('current');
// $(headerNavs[i]).addClass('current');
currentScrollHigh = scrollTop;
currentContentScrollHigh = headerHightALL[i];
currentIndex = i;
var mxxHeight = $("#AnchorContent").height()
var mscrollTop1 = $("#AnchorContent").scrollTop(); // 当前标签的高度
console.log("zukgit2 currentIndex="+ currentIndex );
console.log("zukgit2 windows.scrollTop="+ scrollTop );
console.log("zukgit2 $(window).height()="+ $(window).height() );
console.log("zukgit2 currentContentScrollHigh="+ currentContentScrollHigh );
console.log("zukgit2 AnchorContent.mscrollTop="+ mscrollTop1 );
console.log("zukgit2 AnchorContent.high="+ mxxHeight );
if((currentContentScrollHigh - mscrollTop1)> ($(window).height()/2)){ //↓ // 如果当前index超出 界面的高度
//var sum = Math.floor(headerHightALL(i) / $(window).height());
var dif = currentContentScrollHigh - mscrollTop1;
$("#AnchorContent").animate({scrollTop: (currentContentScrollHigh)}, 50);
console.log(" i(zukgit3) = "+ i +" currentContentScrollHigh =" + currentContentScrollHigh +" mscrollTop1 = "+ mscrollTop1);
console.log(" $(window).height()="+ $(window).height() );

} else if( ( mscrollTop1 - currentContentScrollHigh )> 0 ){ //↑
$("#AnchorContent").animate({scrollTop: currentContentScrollHigh-($("#AnchorContent").height()/3)}, 50);
console.log(" i(zukgit4) = "+ i +" currentContentScrollHigh =" + currentContentScrollHigh +" mscrollTop1 = "+ mscrollTop1);
console.log(" $(window).height()="+ $(window).height() +" $(#AnchorContent).height()="+mxxHeight);
}

return false;
}
}
);
if(scrollTop == 0){
$("#AnchorContent").animate({scrollTop: 0}, 50);
}
if(scrollTop == $(document).height()){
$("#AnchorContent").animate({scrollTop: headerHightALL[headerHightALL.length-1]}, 50);
}
});


$.each(headerNavs, function(i, n){
var high = $(n).offset().top;
headerHightALL.push(high);
console.log("high"+high);
}
);


headerTops = [];
$.each(headerNavs, function(i, n){

$(n).trigger("click");
document.querySelector(headerIDALL[i]).scrollIntoView(true);

var scrollTop = $(window).scrollTop();

headerTops.push(scrollTop);
console.log("headerNavs_index="+i +" scrollTop="+scrollTop +" headerTops="+headerTops[i]);

}
);

  

headerTops.push($(document).height());
$(headerNavs[0]).trigger("click");
document.querySelector(headerIDALL[0]).scrollIntoView(true);

  


var xcontentWidth = $("#AnchorContent").width();
var xWidth = $(window).width();
var xlength = xWidth - xcontentWidth;
$("body").css("marginLeft",xcontentWidth+'px');
$("body").css("max-width",xlength);

if(!showNavBar){
$('.BlogAnchor').hide();
}
if(!expandNavBar){
$(this).html("目录▼");
$(this).attr({"title":"展开"});
$("#AnchorContent").hide();
}
});
</script>
<style>
/*导航*/
.BlogAnchor {
//background: #f1f1f1;
background: #ffffff;
//padding: 10px;
line-height: 180%;
position: fixed;
left: 0px; // right: 48px;
top: 0px;
border-right: 1px solid #aaaaaa;
width: 20%;
height:100%;
}
.BlogAnchor p {
font-size: 18px;
color: #15a230;
margin: 0 0 0.3rem 0;
text-align: right;
}
.BlogAnchor .AnchorContent {
//padding: 5px 0px;
overflow: auto;

}
.BlogAnchor li{
text-indent: 0.5rem;
font-size: 14px;
list-style: none;
}
.BlogAnchor li .nav_item{
padding: 3px;
}
.BlogAnchor li .item_h1{
margin-left: 0rem;
}
.BlogAnchor li .item_h2{
margin-left: 1rem;
font-size: 0.8rem;
}

.BlogAnchor li .item_h3{
margin-left: 2rem;
font-size: 0.8rem;
}

.BlogAnchor li .item_h4{
margin-left: 5rem;
font-size: 0.8rem;
}

.BlogAnchor li .item_h5{
margin-left: 6rem;
font-size: 0.8rem;
}


.BlogAnchor li .item_h6{
margin-left: 7rem;
font-size: 0.8rem;
}

.BlogAnchor li .nav_item.current{
color: white;
background-color: #409eff;

}
.BlogAnchor li .nav_item{
display: block;
}
#AnchorContentToggle {
font-size: 13px;
font-weight: normal;
color: #FFF;
display: inline-block;
line-height: 20px;
background: #409eff;
font-style: normal;
padding: 1px 8px;
}
.BlogAnchor a:hover {
color: #409eff;
}
.BlogAnchor a {
text-decoration: none;
}

ol{
    padding-left: 25px;
}
ol ul{
    padding-left: 14px;
}

.AnchorContent li a{
    color: #333
}
</style>

</body>
</html>
